7145683 explore general pkg performance improvements s11u1b11
authorShawn Walker <shawn.walker@oracle.com>
Wed, 29 Feb 2012 11:37:15 -0800
changeset 2639 06a370373267
parent 2638 4cc18217756f
child 2640 d76034097827
7145683 explore general pkg performance improvements
src/modules/_varcet.c
src/modules/actions/__init__.py
src/modules/actions/_actions.c
src/modules/actions/_common.c
src/modules/actions/attribute.py
src/modules/actions/depend.py
src/modules/actions/directory.py
src/modules/actions/driver.py
src/modules/actions/file.py
src/modules/actions/generic.py
src/modules/actions/group.py
src/modules/actions/hardlink.py
src/modules/actions/legacy.py
src/modules/actions/license.py
src/modules/actions/link.py
src/modules/actions/signature.py
src/modules/actions/unknown.py
src/modules/actions/user.py
src/modules/bundle/SolarisPackageDatastreamBundle.py
src/modules/bundle/SolarisPackageDirBundle.py
src/modules/catalog.py
src/modules/client/api.py
src/modules/client/image.py
src/modules/client/imageplan.py
src/modules/client/pkg_solver.py
src/modules/facet.py
src/modules/flavor/base.py
src/modules/fmri.py
src/modules/lint/pkglint_action.py
src/modules/manifest.py
src/modules/p5p.py
src/modules/publish/dependencies.py
src/modules/variant.py
src/modules/version.py
src/pkg/manifests/package:pkg.p5m
src/pkgdep.py
src/setup.py
src/tests/api/t_action.py
src/tests/api/t_manifest.py
src/tests/cli/t_pkgdep.py
src/tests/perf/actionbench.py
src/tests/perf/fmribench.py
src/util/publish/pkgdiff.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/modules/_varcet.c	Wed Feb 29 11:37:15 2012 -0800
@@ -0,0 +1,189 @@
+/*
+ * 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) 2012, Oracle and/or its affiliates. All rights reserved.
+ */
+
+#include <Python.h>
+
+/*ARGSUSED*/
+static PyObject *
+_allow_facet(PyObject *self, PyObject *args)
+{
+	PyObject *action = NULL;
+	PyObject *facets = NULL;
+	PyObject *keylist = NULL;
+
+	PyObject *act_attrs = NULL;
+	PyObject *attr = NULL;
+	PyObject *value = NULL;
+
+	PyObject *res = NULL;
+	PyObject *ret = Py_True;
+	Py_ssize_t fpos = 0;
+	Py_ssize_t klen = 0;
+
+	if (!PyArg_UnpackTuple(args, "_allow_facet", 2, 2, &facets, &action))
+		return (NULL);
+
+	if ((act_attrs = PyObject_GetAttrString(action, "attrs")) == NULL)
+		return (NULL);
+
+	if ((keylist = PyObject_GetAttrString(facets,
+	    "_Facets__keylist")) == NULL) {
+		Py_DECREF(act_attrs);
+		return (NULL);
+	}
+	klen = PyList_GET_SIZE(keylist);
+
+	if ((res = PyObject_GetAttrString(facets, "_Facets__res")) == NULL) {
+		Py_DECREF(act_attrs);
+		Py_DECREF(keylist);
+		return (NULL);
+	}
+
+#define	CLEANUP_FREFS \
+	Py_DECREF(act_attrs);\
+	Py_DECREF(keylist);\
+	Py_DECREF(res);
+
+	while (PyDict_Next(act_attrs, &fpos, &attr, &value)) {
+		char *as = PyString_AS_STRING(attr);
+		if (strncmp(as, "facet.", 6) != 0)
+			continue;
+
+		PyObject *facet = PyDict_GetItem(facets, attr);
+		if (facet == Py_True) {
+			CLEANUP_FREFS;
+			Py_INCREF(facet);
+			return (facet);
+		}
+
+		if (facet == NULL) {
+			Py_ssize_t idx = 0;
+
+			/*
+			 * Facet is unknown; see if it matches one of the
+			 * wildcard patterns set.
+			 */
+			for (idx = 0; idx < klen; idx++) {
+				PyObject *key = PyList_GET_ITEM(keylist, idx);
+				PyObject *re = PyDict_GetItem(res, key);
+				PyObject *match = PyObject_CallMethod(re,
+				    "match", "O", attr);
+				if (match != Py_None) {
+					PyObject *fval = PyDict_GetItem(
+					    facets, key);
+
+					Py_DECREF(match);
+					CLEANUP_FREFS;
+					if (fval == NULL)
+						return (NULL);
+					Py_INCREF(fval);
+					return (fval);
+				}
+				Py_DECREF(match);
+			}
+
+			/*
+			 * If facet is unknown to the system and no facet
+			 * patterns matched it, be inclusive and allow the
+			 * action.
+			 */
+			CLEANUP_FREFS;
+			Py_RETURN_TRUE;
+		}
+
+		/* Facets are currently OR'd. */
+		ret = Py_False;
+	}
+
+	CLEANUP_FREFS;
+	Py_INCREF(ret);
+	return (ret);
+}
+
+/*ARGSUSED*/
+static PyObject *
+_allow_variant(PyObject *self, PyObject *args)
+{
+	PyObject *action = NULL;
+	PyObject *vars = NULL;
+	PyObject *act_attrs = NULL;
+	PyObject *attr = NULL;
+	PyObject *value = NULL;
+	Py_ssize_t pos = 0;
+
+	if (!PyArg_UnpackTuple(args, "_allow_variant", 2, 2, &vars, &action))
+		return (NULL);
+
+	if ((act_attrs = PyObject_GetAttrString(action, "attrs")) == NULL)
+		return (NULL);
+
+	while (PyDict_Next(act_attrs, &pos, &attr, &value)) {
+		char *as = PyString_AS_STRING(attr);
+		if (strncmp(as, "variant.", 8) == 0) {
+			PyObject *sysv = PyDict_GetItem(vars, attr);
+			char *av = PyString_AsString(value);
+			char *sysav = NULL;
+
+			if (sysv == NULL) {
+				/*
+				 * If system variant value doesn't exist, then
+				 * allow the action if it is a debug variant
+				 * that is "false".
+				 */
+				if ((strncmp(as, "variant.debug.", 14) == 0) &&
+				    (strncmp(av, "false", 5) != 0)) {
+					Py_DECREF(act_attrs);
+					Py_RETURN_FALSE;
+				}
+				continue;
+			}
+
+			sysav = PyString_AsString(sysv);
+			if (strcmp(av, sysav) != 0) {
+				/*
+				 * If system variant value doesn't match action
+				 * variant value, don't allow this action.
+				 */
+				Py_DECREF(act_attrs);
+				Py_RETURN_FALSE;
+			}
+		}
+	}
+
+	Py_DECREF(act_attrs);
+	Py_RETURN_TRUE;
+}
+
+static PyMethodDef methods[] = {
+	{ "_allow_facet", (PyCFunction)_allow_facet, METH_VARARGS },
+	{ "_allow_variant", (PyCFunction)_allow_variant, METH_VARARGS },
+	{ NULL, NULL, 0, NULL }
+};
+
+PyMODINIT_FUNC
+init_varcet(void)
+{
+	Py_InitModule("_varcet", methods);
+}
--- a/src/modules/actions/__init__.py	Wed Feb 29 11:26:51 2012 -0800
+++ b/src/modules/actions/__init__.py	Wed Feb 29 11:37:15 2012 -0800
@@ -21,7 +21,7 @@
 #
 
 #
-# Copyright (c) 2007, 2011, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2007, 2012, Oracle and/or its affiliates. All rights reserved.
 #
 
 """
@@ -113,13 +113,14 @@
                 marker = " " * (4 + self.position) + "^"
                 if hasattr(self, "fmri") and self.fmri is not None:
                         return _("Malformed action in package '%(fmri)s' at "
-                            "position: %(pos)d:\n    %(action)s\n"
+                            "position: %(pos)d: %(error)s:\n    %(action)s\n"
                             "%(marker)s") % { "fmri": self.fmri,
                             "pos": self.position, "action": self.actionstr,
-                            "marker": marker }
-                return _("Malformed action at position: %(pos)d:\n    "
+                            "marker": marker, "error": self.errorstr }
+                return _("Malformed action at position: %(pos)d: %(error)s:\n    "
                     "%(action)s\n%(marker)s") % { "pos": self.position,
-                    "action": self.actionstr, "marker": marker }
+                    "action": self.actionstr, "marker": marker,
+                    "error": self.errorstr }
 
 
 class ActionDataError(ActionError):
@@ -153,6 +154,32 @@
                         "action": self.actionstr, "error": self.errorstr }
 
 
+class MissingKeyAttributeError(InvalidActionError):
+        """Used to indicate that an action's key attribute is missing."""
+
+        def __init__(self, *args):
+                InvalidActionError.__init__(self, str(args[0]),
+                    _("no value specified for key attribute '%s'") % args[1])
+
+
+class KeyAttributeMultiValueError(InvalidActionError):
+        """Used to indicate that an action's key attribute was specified
+        multiple times for an action that expects it only once."""
+
+        def __init__(self, *args):
+                InvalidActionError.__init__(self, str(args[0]),
+                    _("%s attribute may only be specified once") % args[1])
+
+
+class InvalidPathAttributeError(InvalidActionError):
+        """Used to indicate that an action's path attribute value was either
+        empty, '/', or not a string."""
+
+        def __init__(self, *args):
+                InvalidActionError.__init__(self, str(args[0]),
+                    _("Empty or invalid path attribute"))
+
+
 class InvalidActionAttributesError(ActionError):
         """Used to indicate that one or more action attributes were invalid."""
 
@@ -184,33 +211,16 @@
                     "act_errors": act_errors }
 
 
-from _actions import _fromstr
+# This must be imported *after* all of the exception classes are defined as
+# _actions module init needs the exception objects.
+from _actions import fromstr
 
 def attrsfromstr(string):
         """Create an attribute dict given a string w/ key=value pairs.
 
         Raises MalformedActionError if the attributes have syntactic problems.
         """
-        return _fromstr("bogus %s" % string)[2]
-
-def fromstr(string, data=None):
-        """Create an action instance based on a str() representation of an
-        action.
-
-        Raises UnknownActionError if the action type is unknown.
-        Raises MalformedActionError if the action has other syntactic problems.
-        """
-
-        atype, ahash, attr_dict = _fromstr(string)
-
-        if atype not in types:
-                raise UnknownActionError(string, atype)
-
-        action = types[atype](data=data, **attr_dict)
-        if ahash:
-                action.hash = ahash
-
-        return action
+        return fromstr("unknown %s" % string).attrs
 
 def internalizelist(atype, args, ahash=None, basedirs=None):
         """Create an action instance based on a sequence of "key=value" strings.
@@ -281,7 +291,7 @@
                 raise InvalidActionError(astr,
                         "%s action cannot have a 'data' attribute" % atype)
 
-        action = types[atype](data=None, **attrs)
+        action = types[atype](**attrs)
         if ahash:
                 action.hash = ahash
 
--- a/src/modules/actions/_actions.c	Wed Feb 29 11:26:51 2012 -0800
+++ b/src/modules/actions/_actions.c	Wed Feb 29 11:37:15 2012 -0800
@@ -20,7 +20,7 @@
  */
 
 /*
- * Copyright (c) 2008, 2011, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2008, 2012, Oracle and/or its affiliates. All rights reserved.
  */
 
 #include <Python.h>
@@ -29,40 +29,47 @@
 
 static PyObject *MalformedActionError;
 static PyObject *InvalidActionError;
+static PyObject *UnknownActionError;
+static PyObject *aclass_attribute;
+static PyObject *aclass_depend;
+static PyObject *aclass_directory;
+static PyObject *aclass_driver;
+static PyObject *aclass_file;
+static PyObject *aclass_group;
+static PyObject *aclass_hardlink;
+static PyObject *aclass_legacy;
+static PyObject *aclass_license;
+static PyObject *aclass_link;
+static PyObject *aclass_signature;
+static PyObject *aclass_unknown;
+static PyObject *aclass_user;
 
-static char *notident = "hash attribute not identical to positional hash";
+static const char *notident = "hash attribute not identical to positional hash";
 
-static int
+static inline int
 add_to_attrs(PyObject *attrs, PyObject *key, PyObject *attr)
 {
-	int contains, ret;
+	int ret;
+	PyObject *list;
+	PyObject *av = PyDict_GetItem(attrs, key);
 
-	contains = PyDict_Contains(attrs, key);
-	if (contains == 0) {
+	if (av == NULL)
 		return (PyDict_SetItem(attrs, key, attr));
-	} else if (contains == 1) {
-		PyObject *av = PyDict_GetItem(attrs, key);
-		Py_INCREF(av);
-		if (PyList_Check(av)) {
-			ret = PyList_Append(av, attr);
-			Py_DECREF(av);
-			return (ret);
-		} else {
-			PyObject *list;
-			if ((list = PyList_New(2)) == NULL)
-				return (-1);
-			PyList_SET_ITEM(list, 0, av);
-			Py_INCREF(attr);
-			PyList_SET_ITEM(list, 1, attr);
-			ret = PyDict_SetItem(attrs, key, list);
-			Py_DECREF(list);
-			return (ret);
-		}
-	} else if (contains == -1)
+
+	if (PyList_CheckExact(av))
+		return (PyList_Append(av, attr));
+
+	if ((list = PyList_New(2)) == NULL)
 		return (-1);
 
-	/* Shouldn't ever get here */
-	return (0);
+	/* PyList_SET_ITEM steals references. */
+	Py_INCREF(av);
+	PyList_SET_ITEM(list, 0, av);
+	Py_INCREF(attr);
+	PyList_SET_ITEM(list, 1, attr);
+	ret = PyDict_SetItem(attrs, key, list);
+	Py_DECREF(list);
+	return (ret);
 }
 
 static void
@@ -89,28 +96,30 @@
 
 /*ARGSUSED*/
 static PyObject *
-_fromstr(PyObject *self, PyObject *args)
+fromstr(PyObject *self, PyObject *args, PyObject *kwdict)
 {
 	char *s = NULL;
 	char *str = NULL;
 	char *hashstr = NULL;
 	char *keystr = NULL;
 	int *slashmap = NULL;
-	int strl;
+	int strl, typestrl;
 	int i, ks, vs, keysize;
 	int smlen, smpos;
 	char quote;
-	PyObject *type = NULL;
+	PyObject *act_args = NULL;
+	PyObject *act_class = NULL;
+	PyObject *act_data = NULL;
+	PyObject *action = NULL;
 	PyObject *hash = NULL;
 	PyObject *attrs = NULL;
-	PyObject *ret = NULL;
 	PyObject *key = NULL;
 	PyObject *attr = NULL;
 	enum {
-		KEY,    /* key            */
-		UQVAL,  /* unquoted value */
-		QVAL,   /* quoted value   */
-		WS      /* whitespace     */
+		KEY,	/* key			*/
+		UQVAL,	/* unquoted value	*/
+		QVAL,	/* quoted value		*/
+		WS	/* whitespace		*/
 	} state;
 
 	/*
@@ -119,25 +128,34 @@
 	 * malformed() or invalid().  Failure to order this properly will cause
 	 * corruption of the exception messages.
 	 */
-#define malformed(msg) set_malformederr(str, i, (msg))
-#define invalid(msg) set_invaliderr(str, (msg))
-#define CLEANUP_REFS \
+#define	malformed(msg) set_malformederr(str, i, (msg))
+#define	invalid(msg) set_invaliderr(str, (msg))
+#define	CLEANUP_REFS \
 	PyMem_Free(str);\
 	Py_XDECREF(key);\
-	Py_XDECREF(type);\
 	Py_XDECREF(attr);\
 	Py_XDECREF(attrs);\
 	Py_XDECREF(hash);\
 	free(hashstr);
 
 	/*
+	 * Positional arguments must be included in the keyword argument list in
+	 * the order you want them to be assigned.  (A subtle point missing from
+	 * the Python documentation.)
+	 */
+	static char *kwlist[] = { "string", "data", NULL };
+
+	/* Assume data=None by default. */
+	act_data = Py_None;
+
+	/*
 	 * The action string is currently assumed to be a stream of bytes that
 	 * are valid UTF-8.  This method works regardless of whether the string
 	 * object provided is a Unicode object, string object, or a character
 	 * buffer.
 	 */
-	if (PyArg_ParseTuple(args, "et#", "utf-8", &str, &strl) == 0) {
-		PyErr_SetString(PyExc_ValueError, "could not parse argument");
+	if (PyArg_ParseTupleAndKeywords(args, kwdict, "et#|O:fromstr", kwlist,
+	    "utf-8", &str, &strl, &act_data) == 0) {
 		return (NULL);
 	}
 
@@ -150,18 +168,70 @@
 		return (NULL);
 	}
 
-	if ((type = PyString_FromStringAndSize(str, s - str)) == NULL) {
+	/*
+	 * The comparisons here are ordered by frequency in which actions are
+	 * most likely to be encountered in usage by the client grouped by
+	 * length.  Yes, a cheap hack to squeeze a tiny bit of additional
+	 * performance out.
+	 */
+	typestrl = s - str;
+	if (typestrl == 4) {
+		if (strncmp(str, "file", 4) == 0)
+			act_class = aclass_file;
+		else if (strncmp(str, "link", 4) == 0)
+			act_class = aclass_link;
+		else if (strncmp(str, "user", 4) == 0)
+			act_class = aclass_user;
+	} else if (typestrl == 6) {
+		if (strncmp(str, "depend", 6) == 0)
+			act_class = aclass_depend;
+		else if (strncmp(str, "driver", 6) == 0)
+			act_class = aclass_driver;
+		else if (strncmp(str, "legacy", 6) == 0)
+			act_class = aclass_legacy;
+	} else if (typestrl == 3) {
+		if (strncmp(str, "set", 3) == 0)
+			act_class = aclass_attribute;
+		else if (strncmp(str, "dir", 3) == 0)
+			act_class = aclass_directory;
+	} else if (typestrl == 8) {
+		if (strncmp(str, "hardlink", 8) == 0)
+			act_class = aclass_hardlink;
+	} else if (typestrl == 7) {
+		if (strncmp(str, "license", 7) == 0)
+			act_class = aclass_license;
+		else if (strncmp(str, "unknown", 7) == 0)
+			act_class = aclass_unknown;
+	} else if (typestrl == 9) {
+		if (strncmp(str, "signature", 9) == 0)
+			act_class = aclass_signature;
+	} else if (typestrl == 5) {
+		if (strncmp(str, "group", 5) == 0)
+			act_class = aclass_group;
+	}
+
+	if (act_class == NULL) {
+		if ((act_args = Py_BuildValue("s#s#", str, strl,
+		    str, typestrl)) != NULL) {
+			PyErr_SetObject(UnknownActionError, act_args);
+			Py_DECREF(act_args);
+			PyMem_Free(str);
+			return (NULL);
+		}
+
+		/*
+		 * Unable to build argument list for exception; so raise
+		 * general type exception instead.
+		 */
+		PyErr_SetString(PyExc_TypeError, "unknown action type");
 		PyMem_Free(str);
 		return (NULL);
 	}
 
-	PyString_InternInPlace(&type);
-
-	ks = vs = s - str;
+	ks = vs = typestrl;
 	state = WS;
 	if ((attrs = PyDict_New()) == NULL) {
 		PyMem_Free(str);
-		Py_DECREF(type);
 		return (NULL);
 	}
 	for (i = s - str; str[i]; i++) {
@@ -174,10 +244,9 @@
 					malformed("whitespace in key");
 					CLEANUP_REFS;
 					return (NULL);
-				}
-				else {
+				} else {
 					if ((hash = PyString_FromStringAndSize(
-						keystr, keysize)) == NULL) {
+					    keystr, keysize)) == NULL) {
 						CLEANUP_REFS;
 						return (NULL);
 					}
@@ -186,13 +255,13 @@
 				}
 			} else if (str[i] == '=') {
 				if ((key = PyString_FromStringAndSize(
-					keystr, keysize)) == NULL) {
+				    keystr, keysize)) == NULL) {
 					CLEANUP_REFS;
 					return (NULL);
 				}
 
 				if (keysize == 4 && strncmp(keystr, "data",
-					keysize) == 0) {
+				    keysize) == 0) {
 					invalid("invalid key: 'data'");
 					CLEANUP_REFS;
 					return (NULL);
@@ -208,8 +277,7 @@
 					malformed("impossible: missing key");
 					CLEANUP_REFS;
 					return (NULL);
-				}
-				else if (++i == strl) {
+				} else if (++i == strl) {
 					malformed("missing value");
 					CLEANUP_REFS;
 					return (NULL);
@@ -222,8 +290,7 @@
 					malformed("missing value");
 					CLEANUP_REFS;
 					return (NULL);
-				}
-				else {
+				} else {
 					state = UQVAL;
 					vs = i;
 				}
@@ -243,7 +310,7 @@
 				 */
 				if (slashmap == NULL) {
 					smlen = 16;
-					slashmap = calloc(smlen, sizeof(int));
+					slashmap = calloc(smlen, sizeof (int));
 					if (slashmap == NULL) {
 						PyMem_Free(str);
 						return (PyErr_NoMemory());
@@ -258,7 +325,7 @@
 				} else if (smpos == smlen - 1) {
 					smlen *= 2;
 					slashmap = realloc(slashmap,
-						smlen * sizeof(int));
+					    smlen * sizeof (int));
 					if (slashmap == NULL) {
 						PyMem_Free(str);
 						return (PyErr_NoMemory());
@@ -304,7 +371,7 @@
 					slashmap = NULL;
 
 					if ((attr = PyString_FromStringAndSize(
-						sattr, attrlen - o)) == NULL) {
+					    sattr, attrlen - o)) == NULL) {
 						free(sattr);
 						CLEANUP_REFS;
 						return (NULL);
@@ -319,7 +386,7 @@
 					}
 				}
 
-				if (!strncmp(keystr, "hash=", 5)) {
+				if (strncmp(keystr, "hash=", 5) == 0) {
 					char *as = PyString_AsString(attr);
 					if (hashstr && strcmp(as, hashstr)) {
 						invalid(notident);
@@ -330,7 +397,8 @@
 					attr = NULL;
 				} else {
 					PyString_InternInPlace(&attr);
-					if (add_to_attrs(attrs, key, attr) == -1) {
+					if (add_to_attrs(attrs, key,
+					    attr) == -1) {
 						CLEANUP_REFS;
 						return (NULL);
 					}
@@ -340,8 +408,9 @@
 			if (str[i] == ' ' || str[i] == '\t') {
 				state = WS;
 				Py_XDECREF(attr);
-				attr = PyString_FromStringAndSize(&str[vs], i - vs);
-				if (!strncmp(keystr, "hash=", 5)) {
+				attr = PyString_FromStringAndSize(&str[vs],
+				    i - vs);
+				if (strncmp(keystr, "hash=", 5) == 0) {
 					char *as = PyString_AsString(attr);
 					if (hashstr && strcmp(as, hashstr)) {
 						invalid(notident);
@@ -352,7 +421,8 @@
 					attr = NULL;
 				} else {
 					PyString_InternInPlace(&attr);
-					if (add_to_attrs(attrs, key, attr) == -1) {
+					if (add_to_attrs(attrs, key,
+					    attr) == -1) {
 						CLEANUP_REFS;
 						return (NULL);
 					}
@@ -371,24 +441,14 @@
 		}
 	}
 
-	if (state == QVAL) {
-		if (slashmap != NULL)
-			free(slashmap);
-
-		malformed("unfinished quoted value");
-		CLEANUP_REFS;
-		return (NULL);
-	}
-	if (state == KEY) {
-		malformed("missing value");
-		CLEANUP_REFS;
-		return (NULL);
-	}
-
+	/*
+	 * UQVAL is the most frequently encountered end-state, so check that
+	 * first to avoid unnecessary state comparisons.
+	 */
 	if (state == UQVAL) {
 		Py_XDECREF(attr);
 		attr = PyString_FromStringAndSize(&str[vs], i - vs);
-		if (!strncmp(keystr, "hash=", 5)) {
+		if (strncmp(keystr, "hash=", 5) == 0) {
 			char *as = PyString_AsString(attr);
 			if (hashstr && strcmp(as, hashstr)) {
 				invalid(notident);
@@ -404,33 +464,77 @@
 				return (NULL);
 			}
 		}
+	} else if (state == QVAL) {
+		if (slashmap != NULL)
+			free(slashmap);
+
+		malformed("unfinished quoted value");
+		CLEANUP_REFS;
+		return (NULL);
+	} else if (state == KEY) {
+		malformed("missing value");
+		CLEANUP_REFS;
+		return (NULL);
 	}
 
 	PyMem_Free(str);
-	if (hash == NULL)
-		hash = Py_None;
-
-	ret = Py_BuildValue("OOO", type, hash, attrs);
 	Py_XDECREF(key);
 	Py_XDECREF(attr);
-	Py_DECREF(type);
+
+	/*
+	 * Action parsing is done; now build the list of arguments to construct
+	 * the object for it.
+	 */
+	if ((act_args = Py_BuildValue("(O)", act_data)) == NULL) {
+		if (hash != NULL && hash != Py_None)
+			Py_DECREF(hash);
+		Py_DECREF(attrs);
+		return (NULL);
+	}
+
+	/*
+	 * Using the cached action class assigned earlier based on the type,
+	 * call the action constructor, set the hash attribute, and then return
+	 * the new action object.
+	 */
+	action = PyObject_Call(act_class, act_args, attrs);
+	Py_DECREF(act_args);
 	Py_DECREF(attrs);
-	if (hash != Py_None)
+	if (action == NULL) {
+		if (hash != NULL && hash != Py_None)
+			Py_DECREF(hash);
+		return (NULL);
+	}
+
+	if (hash != NULL && hash != Py_None) {
+		if (PyObject_SetAttrString(action, "hash", hash) == -1) {
+			Py_DECREF(hash);
+			Py_DECREF(action);
+			return (NULL);
+		}
 		Py_DECREF(hash);
-	return (ret);
+	}
+
+	return (action);
 }
 
 static PyMethodDef methods[] = {
-	{ "_fromstr", _fromstr, METH_VARARGS },
-	{ NULL, NULL }
+	{ "fromstr", (PyCFunction)fromstr, METH_VARARGS | METH_KEYWORDS },
+	{ NULL, NULL, 0, NULL }
 };
 
 PyMODINIT_FUNC
 init_actions(void)
 {
-	PyObject *sys, *pkg_actions;
-	PyObject *sys_modules;
+	PyObject *action_types = NULL;
+	PyObject *pkg_actions = NULL;
+	PyObject *sys = NULL;
+	PyObject *sys_modules = NULL;
 
+	/*
+	 * Note that module initialization functions are void and may not return
+	 * a value.  However, they should set an exception if appropriate.
+	 */
 	if (Py_InitModule("_actions", methods) == NULL)
 		return;
 
@@ -449,14 +553,68 @@
 		return;
 
 	if ((pkg_actions = PyDict_GetItemString(sys_modules, "pkg.actions"))
-		== NULL) {
+	    == NULL) {
 		/* No exception is set */
 		PyErr_SetString(PyExc_KeyError, "pkg.actions");
+		Py_DECREF(sys_modules);
+		return;
+	}
+	Py_DECREF(sys_modules);
+
+	/*
+	 * Each reference is DECREF'd after retrieval as Python 2.x doesn't
+	 * provide a module shutdown/cleanup hook.  Since these references are
+	 * guaranteed to stay around until the module is unloaded, DECREF'ing
+	 * them now ensures that garbage cleanup will work as expected during
+	 * process exit.  This applies to the action type caching below as well.
+	 */
+	MalformedActionError = \
+	    PyObject_GetAttrString(pkg_actions, "MalformedActionError");
+	Py_DECREF(MalformedActionError);
+	InvalidActionError = \
+	    PyObject_GetAttrString(pkg_actions, "InvalidActionError");
+	Py_DECREF(InvalidActionError);
+	UnknownActionError = \
+	    PyObject_GetAttrString(pkg_actions, "UnknownActionError");
+	Py_DECREF(UnknownActionError);
+
+	/*
+	 * Retrieve the list of action types and then store a reference to each
+	 * class for use during action construction.  (This allows avoiding the
+	 * overhead of retrieving a new reference for each action constructed.)
+	 */
+	if ((action_types = PyObject_GetAttrString(pkg_actions,
+	    "types")) == NULL) {
+		PyErr_SetString(PyExc_KeyError, "pkg.actions.types missing!");
 		return;
 	}
 
-	MalformedActionError = \
-		PyObject_GetAttrString(pkg_actions, "MalformedActionError");
-	InvalidActionError = \
-		PyObject_GetAttrString(pkg_actions, "InvalidActionError");
+	/*
+	 * cache_class borrows the references to the action type objects; this
+	 * is safe as they should remain valid as long as the module is loaded.
+	 * (PyDict_GetItem* doesn't return a new reference.)
+	 */
+#define	cache_class(cache_var, name) \
+	if ((cache_var = PyDict_GetItemString(action_types, name)) == NULL) { \
+		PyErr_SetString(PyExc_KeyError, \
+		    "Action type class missing: " name); \
+		Py_DECREF(action_types); \
+		return; \
+	}
+
+	cache_class(aclass_attribute, "set");
+	cache_class(aclass_depend, "depend");
+	cache_class(aclass_directory, "dir");
+	cache_class(aclass_driver, "driver");
+	cache_class(aclass_file, "file");
+	cache_class(aclass_group, "group");
+	cache_class(aclass_hardlink, "hardlink");
+	cache_class(aclass_legacy, "legacy");
+	cache_class(aclass_license, "license");
+	cache_class(aclass_link, "link");
+	cache_class(aclass_signature, "signature");
+	cache_class(aclass_unknown, "unknown");
+	cache_class(aclass_user, "user");
+
+	Py_DECREF(action_types);
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/modules/actions/_common.c	Wed Feb 29 11:37:15 2012 -0800
@@ -0,0 +1,297 @@
+/*
+ * 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) 2012, Oracle and/or its affiliates. All rights reserved.
+ */
+
+/*
+ * These functions, although common to all actions, could not be placed in
+ * _actions.c due to module import dependencies.
+ */
+#include <Python.h>
+
+static PyObject *nohash;
+
+static void
+set_invalid_action_error(const char *name, PyObject *action,
+    PyObject *key_aname)
+{
+	PyObject *exc = NULL;
+	PyObject *val = NULL;
+	PyObject *pkg_actions = NULL;
+
+	if ((pkg_actions = PyImport_ImportModule("pkg.actions")) == NULL) {
+		/* No exception is set */
+		PyErr_SetString(PyExc_KeyError, "pkg.actions");
+		return;
+	}
+
+	/*
+	 * Obtain a reference to the action exception type so that SetObject can
+	 * build the appropriate exception object using the created list of
+	 * arguments.
+	 */
+	if ((exc = PyObject_GetAttrString(pkg_actions, name)) == NULL) {
+		Py_DECREF(pkg_actions);
+		return;
+	}
+	Py_DECREF(pkg_actions);
+
+	if ((val = Py_BuildValue("OO", action, key_aname)) != NULL) {
+		PyErr_SetObject(exc, val);
+		Py_DECREF(val);
+	}
+	Py_DECREF(exc);
+}
+
+/*
+ * These routines are expected to return NULL in an exception case per CPython
+ * calling conventions.  Whenver NULL is returned, an exception should already
+ * be set, either by the function that was just called and failed, or by the
+ * routine.  If the routine is successful, it is expected to return a PyObject
+ * of some kind even if the return value is ignored by consumers.  The expected
+ * return value is usually None.
+ */
+
+/*ARGSUSED*/
+static inline PyObject *
+_generic_init_common(PyObject *action, PyObject *data, PyObject *attrs)
+{
+	PyObject *key_aname = NULL;
+	PyObject *key_attr = NULL;
+	PyObject *path_attr = NULL;
+	char *path = NULL;
+	char invalid_path = 0;
+
+	/*
+	 * Before doing anything else to the action, action attributes must be
+	 * set as set_data() relies on it.
+	 */
+	if (attrs != NULL) {
+		if (PyObject_SetAttrString(action, "attrs", attrs) == -1)
+			return (NULL);
+	} else {
+		/* Caller didn't specify any keyword arguments. */
+		if ((attrs = PyDict_New()) == NULL)
+			return (NULL);
+		if (PyObject_SetAttrString(action, "attrs", attrs) == -1) {
+			Py_DECREF(attrs);
+			return (NULL);
+		}
+		Py_DECREF(attrs);
+	}
+
+	if (data == NULL || data == Py_None) {
+		/* No need to call set_data(); this is much faster. */
+		if (PyObject_SetAttrString(action, "data", Py_None) == -1)
+			return (NULL);
+	} else {
+		PyObject *res = PyObject_CallMethod(action, "set_data", "(O)",
+		    data);
+		if (res == NULL)
+			return (NULL);
+		Py_DECREF(res);
+	}
+
+	if ((key_aname = PyObject_GetAttrString(action, "key_attr")) == NULL)
+		return (NULL);
+
+	if (key_aname == Py_None) {
+		Py_DECREF(key_aname);
+		Py_RETURN_NONE;
+	}
+
+	if ((key_attr = PyDict_GetItem(attrs, key_aname)) == NULL) {
+		PyObject *aname = PyObject_GetAttrString(action, "name");
+		char *ns = PyString_AS_STRING(aname);
+
+		/*
+		 * set actions allow an alternate value form, so
+		 * AttributeAction.__init__ will fill this in later and raise an
+		 * exception if appropriate.
+		 *
+		 * signature actions can't require their key attribute since the
+		 * value of a signature may not yet be known.
+		 */
+		if (strcmp(ns, "set") != 0 && strcmp(ns, "signature") != 0) {
+			set_invalid_action_error("MissingKeyAttributeError",
+			    action, key_aname);
+			Py_DECREF(key_aname);
+			return (NULL);
+		}
+
+		Py_DECREF(key_aname);
+		Py_RETURN_NONE;
+	}
+
+	if (PyList_CheckExact(key_attr)) {
+		PyObject *aname = PyObject_GetAttrString(action, "name");
+		char *ns = PyString_AS_STRING(aname);
+		int multi_error = 0;
+
+		if (strcmp(ns, "depend") != 0) {
+			/*
+			 * Unless this is a dependency action, multiple values
+			 * are never allowed for key attribute.
+			 */
+			multi_error = 1;
+		} else {
+			PyObject *dt = PyDict_GetItemString(attrs, "type");
+			/*
+			 * If dependency type is 'require-any', multiple values
+			 * are allowed for key attribute.
+			 */
+			if (dt != NULL) {
+				char *ts = PyString_AsString(dt);
+				if (ts == NULL) {
+					Py_DECREF(key_aname);
+					Py_DECREF(aname);
+					return (NULL);
+				}
+				if (strcmp(ts, "require-any") != 0)
+					multi_error = 1;
+			} else {
+				multi_error = 1;
+			}
+		}
+
+		Py_DECREF(aname);
+		if (multi_error == 1) {
+			set_invalid_action_error("KeyAttributeMultiValueError",
+			    action, key_aname);
+			Py_DECREF(key_aname);
+			return (NULL);
+		}
+	}
+
+	if ((path_attr = PyDict_GetItemString(attrs, "path")) == NULL) {
+		Py_DECREF(key_aname);
+		Py_RETURN_NONE;
+	}
+
+	if ((path = PyString_AsString(path_attr)) != NULL) {
+		if (path[0] == '/') {
+			PyObject *stripped = PyObject_CallMethod(
+			    path_attr, "lstrip", "(s)", "/");
+			if (stripped == NULL) {
+				Py_DECREF(key_aname);
+				return (NULL);
+			}
+			if (PyDict_SetItemString(attrs, "path",
+			    stripped) == -1) {
+				Py_DECREF(key_aname);
+				Py_DECREF(stripped);
+				return (NULL);
+			}
+			if (PyString_GET_SIZE(stripped) == 0)
+				invalid_path = 1;
+			Py_DECREF(stripped);
+		} else {
+			if (PyString_GET_SIZE(path_attr) == 0)
+				invalid_path = 1;
+		}
+	} else {
+		/* path attribute is not a string. */
+		invalid_path = 1;
+	}
+
+	if (invalid_path == 1) {
+		set_invalid_action_error("InvalidPathAttributeError",
+		    action, key_aname);
+		Py_DECREF(key_aname);
+		return (NULL);
+	}
+
+	Py_DECREF(key_aname);
+	Py_RETURN_NONE;
+}
+
+/*ARGSUSED*/
+static PyObject *
+_generic_init(PyObject *self, PyObject *args, PyObject *attrs)
+{
+	PyObject *action = NULL;
+	PyObject *data = NULL;
+
+	/* data is optional, but must not be specified as a keyword argument! */
+	if (!PyArg_UnpackTuple(args, "_generic_init", 1, 2, &action, &data))
+		return (NULL);
+
+	return (_generic_init_common(action, data, attrs));
+}
+
+/*ARGSUSED*/
+static PyObject *
+_file_init(PyObject *self, PyObject *args, PyObject *attrs)
+{
+	PyObject *action = NULL;
+	PyObject *data = NULL;
+	PyObject *result = NULL;
+
+	if (!PyArg_UnpackTuple(args, "_file_init", 1, 2, &action, &data))
+		return (NULL);
+
+	if ((result = _generic_init_common(action, data, attrs)) != NULL)
+		Py_DECREF(result);
+	else
+		return (NULL);
+
+	if (PyObject_SetAttrString(action, "hash", nohash) == -1)
+		return (NULL);
+
+	if (PyObject_SetAttrString(action, "replace_required", Py_False) == -1)
+		return (NULL);
+
+	Py_RETURN_NONE;
+}
+
+static PyMethodDef methods[] = {
+	{ "_file_init", (PyCFunction)_file_init, METH_VARARGS | METH_KEYWORDS },
+	{ "_generic_init", (PyCFunction)_generic_init,
+	    METH_VARARGS | METH_KEYWORDS },
+	{ NULL, NULL, 0, NULL }
+};
+
+PyMODINIT_FUNC
+init_common(void)
+{
+	PyObject *pkg_actions = NULL;
+
+	/*
+	 * Note that module initialization functions are void and may not return
+	 * a value.  However, they should set an exception if appropriate.
+	 */
+	if (Py_InitModule("_common", methods) == NULL)
+		return;
+
+	if ((pkg_actions = PyImport_ImportModule("pkg.actions")) == NULL) {
+		/* No exception is set */
+		PyErr_SetString(PyExc_KeyError, "pkg.actions");
+		return;
+	}
+
+	if ((nohash = PyString_FromStringAndSize("NOHASH", 6)) == NULL) {
+		PyErr_SetString(PyExc_ValueError,
+		    "Unable to create nohash string object.");
+		return;
+	}
+}
--- a/src/modules/actions/attribute.py	Wed Feb 29 11:26:51 2012 -0800
+++ b/src/modules/actions/attribute.py	Wed Feb 29 11:37:15 2012 -0800
@@ -41,22 +41,25 @@
 
         name = "set"
         key_attr = "name"
+        ordinality = generic._orderdict[name]
 
         def __init__(self, data=None, **attrs):
                 generic.Action.__init__(self, data, **attrs)
 
-                # For convenience, we allow people to express attributes as
-                # "<name>=<value>", rather than "name=<name> value=<value>", but
-                # we always convert to the latter.
                 try:
-                        if len(attrs) == 1:
-                                self.attrs["name"], self.attrs["value"] = \
-                                    self.attrs.popitem()
+                        self.attrs["name"]
+                        self.attrs["value"]
                 except KeyError:
-                        # Let error check below deal with this.
-                        pass
-
-                if "name" not in self.attrs or "value" not in self.attrs:
+                        # For convenience, we allow people to express attributes as
+                        # "<name>=<value>", rather than "name=<name> value=<value>", but
+                        # we always convert to the latter.
+                        try:
+                                if len(attrs) == 1:
+                                        self.attrs["name"], self.attrs["value"] = \
+                                            self.attrs.popitem()
+                                        return
+                        except KeyError:
+                                pass
                         raise pkg.actions.InvalidActionError(str(self),
                             'Missing "name" or "value" attribute')
 
--- a/src/modules/actions/depend.py	Wed Feb 29 11:26:51 2012 -0800
+++ b/src/modules/actions/depend.py	Wed Feb 29 11:37:15 2012 -0800
@@ -96,18 +96,19 @@
 
         name = "depend"
         key_attr = "fmri"
+        ordinality = generic._orderdict[name]
 
         def __init__(self, data=None, **attrs):
                 generic.Action.__init__(self, data, **attrs)
-                if "type" not in self.attrs:
+                try:
+                        if self.attrs["type"] not in known_types:
+                                raise pkg.actions.InvalidActionError(str(self),
+                                    _("Unknown type (%s) in depend action") %
+                                    self.attrs["type"])
+                except KeyError:
                         raise pkg.actions.InvalidActionError(
                             str(self), _("Missing type attribute"))
 
-                if self.attrs["type"] not in known_types:
-                        raise pkg.actions.InvalidActionError(str(self),
-                            _("Unknown type (%s) in depend action") %
-                            self.attrs["type"])
-
         def __check_parent_installed(self, image, fmri):
 
                 if not image.linked.ischild():
--- a/src/modules/actions/directory.py	Wed Feb 29 11:26:51 2012 -0800
+++ b/src/modules/actions/directory.py	Wed Feb 29 11:37:15 2012 -0800
@@ -21,7 +21,7 @@
 #
 
 #
-# Copyright (c) 2007, 2011, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2007, 2012, Oracle and/or its affiliates. All rights reserved.
 #
 
 """module describing a directory packaging object
@@ -48,6 +48,7 @@
         globally_identical = True
         refcountable = True
         namespace_group = "path"
+        ordinality = generic._orderdict[name]
 
         def compare(self, other):
                 return cmp(self.attrs["path"], other.attrs["path"])
--- a/src/modules/actions/driver.py	Wed Feb 29 11:26:51 2012 -0800
+++ b/src/modules/actions/driver.py	Wed Feb 29 11:37:15 2012 -0800
@@ -45,6 +45,7 @@
         name = "driver"
         key_attr = "name"
         globally_identical = True
+        ordinality = generic._orderdict[name]
 
         usr_sbin = None
         add_drv = None
--- a/src/modules/actions/file.py	Wed Feb 29 11:26:51 2012 -0800
+++ b/src/modules/actions/file.py	Wed Feb 29 11:37:15 2012 -0800
@@ -29,13 +29,15 @@
 This module contains the FileAction class, which represents a file-type
 packaging object."""
 
+import errno
+import generic
 import os
-import errno
+import stat
 import tempfile
-import stat
-import generic
+import types
 import zlib
 
+import _common
 import pkg.actions
 import pkg.client.api_errors as api_errors
 import pkg.misc as misc
@@ -59,13 +61,12 @@
         unique_attrs = "path", "mode", "owner", "group", "preserve"
         globally_identical = True
         namespace_group = "path"
+        ordinality = generic._orderdict[name]
 
         has_payload = True
 
-        def __init__(self, data=None, **attrs):
-                generic.Action.__init__(self, data, **attrs)
-                self.hash = "NOHASH"
-                self.replace_required = False
+        # __init__ is provided as a native function (see end of class
+        # declaration).
 
         # this check is only needed on Windows
         if portable.ostype == "windows":
@@ -509,25 +510,15 @@
                 # Attempt to remove the file.
                 self.remove_fsobj(pkgplan, path)
 
-        def different(self, other):
+        def different(self, other, cmp_hash=True):
                 # Override the generic different() method to ignore the file
                 # hash for ELF files and compare the ELF hash instead.
                 # XXX This should be modularized and controlled by policy.
 
                 # One of these isn't an ELF file, so call the generic method
-                if "elfhash" not in self.attrs or "elfhash" not in other.attrs:
-                        return generic.Action.different(self, other)
-
-                sset = set(self.attrs.keys())
-                oset = set(other.attrs.keys())
-                if sset.symmetric_difference(oset):
-                        return True
-
-                for a in self.attrs:
-                        if self.attrs[a] != other.attrs[a]:
-                                return True
-
-                return False
+                if "elfhash" in self.attrs and "elfhash" in other.attrs:
+                        cmp_hash = False
+                return generic.Action.different(self, other, cmp_hash=cmp_hash)
 
         def generate_indices(self):
                 """Generates the indices needed by the search dictionary.  See
@@ -600,3 +591,5 @@
                 if errors:
                         raise pkg.actions.InvalidActionAttributesError(self,
                             errors, fmri=fmri)
+
+FileAction.__init__ = types.MethodType(_common._file_init, None, FileAction)
--- a/src/modules/actions/generic.py	Wed Feb 29 11:26:51 2012 -0800
+++ b/src/modules/actions/generic.py	Wed Feb 29 11:37:15 2012 -0800
@@ -37,14 +37,22 @@
         os.SEEK_SET
 except AttributeError:
         os.SEEK_SET, os.SEEK_CUR, os.SEEK_END = range(3)
+import stat
+import types
+
+import _common
 import pkg.actions
 import pkg.client.api_errors as apx
 import pkg.portable as portable
 import pkg.variant as variant
-import stat
 
-# Used to define sort order between action types.
-_ORDER_DICT_LIST = [
+# Directories must precede all filesystem object actions; hardlinks must follow
+# all filesystem object actions (except links).  Note that user and group
+# actions precede file actions (so that the system permits chown'ing them to
+# users and groups that may be delivered during the same operation); this
+# implies that /etc/group and /etc/passwd file ownership needs to be part of
+# initial contents of those files.
+_orderdict = dict((k, i) for i, k in enumerate((
     "set",
     "depend",
     "group",
@@ -55,9 +63,10 @@
     "link",
     "driver",
     "unknown",
+    "license",
     "legacy",
     "signature"
-]
+)))
 
 # EmptyI for argument defaults; no import to avoid pkg.misc dependency.
 EmptyI = tuple()
@@ -119,7 +128,7 @@
         files.
         """
 
-        __slots__ = ["attrs", "data", "ord"]
+        __slots__ = ["attrs", "data"]
 
         # 'name' is the name of the action, as specified in a manifest.
         name = "generic"
@@ -140,110 +149,23 @@
         # the sole members of their group, this is set to a non-None value for
         # subclasses by the NSG metaclass.
         namespace_group = None
+        # 'ordinality' is a numeric value that is used during action comparison
+        # to determine action sorting.
+        ordinality = _orderdict["unknown"]
         # 'unique_attrs' is a tuple listing the attributes which must be
         # identical in order for an action to be safely delivered multiple times
         # (for those that can be).
         unique_attrs = ()
 
-        # the following establishes the sort order between action types.
-        # Directories must precede all
-        # filesystem-modifying actions; hardlinks must follow all
-        # filesystem-modifying actions.  Note that usr/group actions
-        # precede file actions; this implies that /etc/group and /etc/passwd
-        # file ownership needs to be part of initial contents of those files
-        orderdict = {}
-        unknown = 0
-
         # The version of signature used.
         sig_version = 0
         # Most types of actions do not have a payload.
         has_payload = False
 
-        # By default, leading slashes should be stripped from "path" attribute.
-        _strip_path = True
-
         __metaclass__ = NSG
 
-        def loadorderdict(self):
-                self.orderdict.update(dict((
-                    (pkg.actions.types[t], i)
-                    for i, t in enumerate(_ORDER_DICT_LIST)
-                )))
-                self.__class__.unknown = \
-                    self.orderdict[pkg.actions.types["unknown"]]
-
-        def __init__(self, data=None, **attrs):
-                """Action constructor.
-
-                The optional 'data' argument may be either a string, a file-like
-                object, or a callable.  If it is a string, then it will be
-                substituted with a callable that will return an open handle to
-                the file represented by the string.  Otherwise, if it is not
-                already a callable, it is assumed to be a file-like object, and
-                will be substituted with a callable that will return the object.
-                If it is a callable, it will not be replaced at all.
-
-                Any remaining named arguments will be treated as attributes.
-                """
-
-                if not self.orderdict:
-                        self.loadorderdict()
-
-
-                # A try/except is used here instead of get() as it is
-                # consistently 2% faster.
-                try:
-                        self.ord = self.orderdict[type(self)]
-                except KeyError:
-                        self.ord = self.unknown
-
-                self.attrs = attrs
-
-                # Since this is a hot path, avoid a function call unless
-                # absolutely necessary.
-                if data is None:
-                        self.data = None
-                else:
-                        self.set_data(data)
-
-                if not self.key_attr:
-                        # Nothing more to do.
-                        return
-
-                # Test if method only string object will have is defined to
-                # determine if key attribute has been specified multiple times.
-                try:
-                        self.attrs[self.key_attr].decode
-                except KeyError:
-                        if self.name == "set" or self.name == "signature":
-                                # Special case for set and signature actions
-                                # since they allow two forms of syntax.
-                                return
-                        raise pkg.actions.InvalidActionError(str(self),
-                           _("no value specified for key attribute '%s'") %
-                           self.key_attr)
-                except AttributeError:
-                       if self.name != "depend" or \
-                           self.attrs.get("type") != "require-any":
-                                # Assume list since fromstr() will only ever
-                                # provide a string or list and decode method
-                                # wasn't found.  This is much faster than
-                                # checking isinstance or 'type() ==' in
-                                # a hot path.
-                                raise pkg.actions.InvalidActionError(str(self),
-                                   _("%s attribute may only be specified "
-                                   "once") % self.key_attr)
-
-                if self._strip_path:
-                        # Strip leading slash from path if requested.
-                        try:
-                                self.attrs["path"] = \
-                                    self.attrs["path"].lstrip("/")
-                        except KeyError:
-                                return
-                        if not self.attrs["path"]:
-                                raise pkg.actions.InvalidActionError(
-                                    str(self), _("Empty path attribute"))
+        # __init__ is provided as a native function (see end of class
+        # declaration).
 
         def set_data(self, data):
                 """This function sets the data field of the action.
@@ -330,18 +252,33 @@
                 computed.  This may need to be done externally.
                 """
 
+                sattrs = self.attrs.keys()
                 out = self.name
-                if hasattr(self, "hash") and self.hash is not None:
-                        if "=" not in self.hash:
-                                out += " " + self.hash
-                        else:
-                                self.attrs["hash"] = self.hash
+                try:
+                        h = self.hash
+                        if h:
+                                if "=" not in h:
+                                        out += " " + h
+                                else:
+                                        sattrs.append("hash")
+                except AttributeError:
+                        # No hash to stash.
+                        pass
 
                 # Sort so that we get consistent action attribute ordering.
                 # We pay a performance penalty to do so, but it seems worth it.
-                for k in sorted(self.attrs.keys()):
-                        v = self.attrs[k]
-                        if isinstance(v, list) or isinstance(v, set):
+                sattrs.sort()
+
+                for k in sattrs:
+                        try:
+                               v = self.attrs[k]
+                        except KeyError:
+                                # If we can't find the attribute, it must be the
+                                # hash. 'h' will only be in scope if the block
+                                # at the start succeeded.
+                                v = h
+
+                        if type(v) is list:
                                 out += " " + " ".join([
                                     "=".join((k, quote_attr_value(lmt)))
                                     for lmt in v
@@ -357,10 +294,6 @@
                         else:
                                 out += " " + k + "=" + v
 
-                # If we have a hash attribute, it's because we added it above;
-                # get rid of it now.
-                self.attrs.pop("hash", None)
-
                 return out
 
         def __repr__(self):
@@ -402,7 +335,7 @@
                 # We pay a performance penalty to do so, but it seems worth it.
                 for k in sorted(self.attrs.keys()):
                         v = self.attrs[k]
-                        if isinstance(v, list) or isinstance(v, set):
+                        if type(v) is list:
                                 out += " " + " ".join([
                                     "%s=%s" % (k, q(lmt)) for lmt in sorted(v)
                                 ])
@@ -439,20 +372,16 @@
                 """Compare actions for ordering.  The ordinality of a
                    given action is computed and stored at action
                    initialization."""
-                if not isinstance(other, Action):
-                        return NotImplemented
 
-                res = cmp(self.ord, other.ord)
-
+                res = cmp(self.ordinality, other.ordinality)
                 if res == 0:
                         return self.compare(other) # often subclassed
-
                 return res
 
         def compare(self, other):
                 return cmp(id(self), id(other))
 
-        def different(self, other):
+        def different(self, other, cmp_hash=True):
                 """Returns True if other represents a non-ignorable change from
                 self.
 
@@ -463,25 +392,35 @@
 
                 # We could ignore key_attr, or possibly assert that it's the
                 # same.
-                sset = set(self.attrs.keys())
-                oset = set(other.attrs.keys())
+                sattrs = self.attrs
+                oattrs = other.attrs
+                sset = set(sattrs.iterkeys())
+                oset = set(oattrs.iterkeys())
                 if sset.symmetric_difference(oset):
                         return True
 
-                for a in self.attrs:
-                        x = self.attrs[a]
-                        y = other.attrs[a]
-                        if isinstance(x, list) and \
-                            isinstance(y, list):
-                                if sorted(x) != sorted(y):
+                for a, x in sattrs.iteritems():
+                        y = oattrs[a]
+                        if x != y:
+                                if len(x) == len(y) and \
+                                    type(x) is list and type(y) is list:
+                                        if sorted(x) != sorted(y):
+                                                return True
+                                else:
                                         return True
-                        elif x != y:
-                                return True
 
-                if hasattr(self, "hash"):
-                        assert(hasattr(other, "hash"))
-                        if self.hash != other.hash:
-                                return True
+                if cmp_hash:
+                        shash = ohash = None
+                        try:
+                                shash = self.hash
+                                ohash = other.hash
+                                if shash != other.hash:
+                                        return True
+                        except AttributeError:
+                                if shash or ohash:
+                                        raise AssertionError("attempt to "
+                                            "compare a %s action to a %s "
+                                            "action") % (self.name, other.name)
 
                 return False
 
@@ -655,15 +594,12 @@
                 """Return the names of any facet or variant tags in this
                 action."""
 
-                variants = []
-                facets = []
-
-                for k in self.attrs.iterkeys():
-                        if k[:8] == "variant.":
-                                variants.append(k)
-                        if k[:6] == "facet.":
-                                facets.append(k)
-                return variants, facets
+                # Hot path; grab reference to attrs and use list comprehensions
+                # to construct the results.  This is faster than iterating over
+                # attrs once and appending to two lists separately.
+                attrs = self.attrs
+                return [k for k in attrs if k[:8] == "variant."], \
+                    [k for k in attrs if k[:6] == "facet."]
 
         def get_variant_template(self):
                 """Return the VariantCombinationTemplate that the variant tags
@@ -953,11 +889,13 @@
 
         def attrlist(self, name):
                 """return list containing value of named attribute."""
-                value = self.attrs.get(name, [])
-                if isinstance(value, list):
-                        return value
-                else:
-                        return [ value ]
+                try:
+                        value = self.attrs[name]
+                except KeyError:
+                        return [] 
+                if type(value) is not list:
+                        return [value]
+                return value
 
         def directory_references(self):
                 """Returns references to paths in action."""
@@ -1096,9 +1034,8 @@
                 errors = []
                 for attr in self.attrs:
                         if ((attr.startswith("facet.") or
-                            attr.startswith("variant.") or
                             attr == "reboot-needed" or attr in single_attrs) and
-                            isinstance(self.attrs[attr], list)):
+                            type(self.attrs[attr]) is list):
                                 errors.append((attr, _("%s may only be "
                                     "specified once") % attr))
                         elif attr in numeric_attrs:
@@ -1163,3 +1100,5 @@
                     "try again.") % locals()
                 raise apx.ActionExecutionError(self, details=err_txt,
                     fmri=fmri)
+
+Action.__init__ = types.MethodType(_common._generic_init, None, Action)
--- a/src/modules/actions/group.py	Wed Feb 29 11:26:51 2012 -0800
+++ b/src/modules/actions/group.py	Wed Feb 29 11:37:15 2012 -0800
@@ -21,7 +21,7 @@
 #
 
 #
-# Copyright (c) 2008, 2011, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2008, 2012, Oracle and/or its affiliates. All rights reserved.
 #
 
 """module describing a user packaging object
@@ -50,9 +50,7 @@
         name = "group"
         key_attr = "groupname"
         globally_identical = True
-
-        def __init__(self, data=None, **attrs):
-                generic.Action.__init__(self, data, **attrs)
+        ordinality = generic._orderdict[name]
 
         def extract(self, attrlist):
                 """ return a dictionary containing attrs in attr list
--- a/src/modules/actions/hardlink.py	Wed Feb 29 11:26:51 2012 -0800
+++ b/src/modules/actions/hardlink.py	Wed Feb 29 11:37:15 2012 -0800
@@ -21,7 +21,7 @@
 #
 
 #
-# Copyright (c) 2007, 2011, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2007, 2012, Oracle and/or its affiliates. All rights reserved.
 #
 
 """module describing a (hard) link packaging object
@@ -30,6 +30,7 @@
 packaging object."""
 
 import errno
+import generic
 import link
 import os
 import stat
@@ -43,10 +44,7 @@
         __slots__ = []
 
         name = "hardlink"
-
-        # Tells parent class that leading slash should not be stripped
-        # from "path" attribute.
-        _strip_path = False
+        ordinality = generic._orderdict[name]
 
         def get_target_path(self):
                 """ return a path for target that is relative to image"""
--- a/src/modules/actions/legacy.py	Wed Feb 29 11:26:51 2012 -0800
+++ b/src/modules/actions/legacy.py	Wed Feb 29 11:37:15 2012 -0800
@@ -21,7 +21,7 @@
 #
 
 #
-# Copyright (c) 2007, 2011, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2007, 2012, Oracle and/or its affiliates. All rights reserved.
 #
 
 """module describing a legacy packaging object
@@ -50,6 +50,7 @@
             "version", "basedir", "pkginst", "pstamp", "sunw_prodvers")
         refcountable = True
         globally_identical = True
+        ordinality = generic._orderdict[name]
 
         def directory_references(self):
                 return [os.path.normpath(os.path.join("var/sadm/pkg",
--- a/src/modules/actions/license.py	Wed Feb 29 11:26:51 2012 -0800
+++ b/src/modules/actions/license.py	Wed Feb 29 11:37:15 2012 -0800
@@ -54,6 +54,7 @@
         reverse_indices = ("license", )
         refcountable = True
         globally_identical = True
+        ordinality = generic._orderdict[name]
 
         has_payload = True
 
--- a/src/modules/actions/link.py	Wed Feb 29 11:26:51 2012 -0800
+++ b/src/modules/actions/link.py	Wed Feb 29 11:37:15 2012 -0800
@@ -21,7 +21,7 @@
 #
 
 #
-# Copyright (c) 2007, 2011, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2007, 2012, Oracle and/or its affiliates. All rights reserved.
 #
 
 """module describing a (symbolic) link packaging object
@@ -51,6 +51,7 @@
         globally_identical = True
         refcountable = True
         namespace_group = "path"
+        ordinality = generic._orderdict[name]
 
         def install(self, pkgplan, orig):
                 """Client-side method that installs a link."""
--- a/src/modules/actions/signature.py	Wed Feb 29 11:26:51 2012 -0800
+++ b/src/modules/actions/signature.py	Wed Feb 29 11:37:15 2012 -0800
@@ -45,6 +45,7 @@
 
         name = "signature"
         key_attr = "value"
+        ordinality = generic._orderdict[name]
 
         def __init__(self, data, **attrs):
                 generic.Action.__init__(self, data, **attrs)
--- a/src/modules/actions/unknown.py	Wed Feb 29 11:26:51 2012 -0800
+++ b/src/modules/actions/unknown.py	Wed Feb 29 11:37:15 2012 -0800
@@ -21,7 +21,7 @@
 #
 
 #
-# Copyright (c) 2008, 2011, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2008, 2012, Oracle and/or its affiliates. All rights reserved.
 #
 
 """module describing a unknown packaging object
@@ -39,3 +39,4 @@
         __slots__ = []
 
         name = "unknown"
+        ordinality = generic._orderdict[name]
--- a/src/modules/actions/user.py	Wed Feb 29 11:26:51 2012 -0800
+++ b/src/modules/actions/user.py	Wed Feb 29 11:37:15 2012 -0800
@@ -21,7 +21,7 @@
 #
 
 #
-# Copyright (c) 2008, 2011, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2008, 2012, Oracle and/or its affiliates. All rights reserved.
 #
 
 """module describing a user packaging object
@@ -48,6 +48,7 @@
         name = "user"
         key_attr = "username"
         globally_identical = True
+        ordinality = generic._orderdict[name]
 
         # if these values are different on disk than in action
         # prefer on-disk version
--- a/src/modules/bundle/SolarisPackageDatastreamBundle.py	Wed Feb 29 11:26:51 2012 -0800
+++ b/src/modules/bundle/SolarisPackageDatastreamBundle.py	Wed Feb 29 11:37:15 2012 -0800
@@ -21,7 +21,7 @@
 #
 
 #
-# Copyright (c) 2007, 2011, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2007, 2012, Oracle and/or its affiliates. All rights reserved.
 #
 
 import os
@@ -166,7 +166,7 @@
                         act = hardlink.HardLinkAction(path=mapline.pathname,
                             target=mapline.target)
                 elif mapline.type == "i" and mapline.pathname == "copyright":
-                        act = license.LicenseAction(data=ci.extractfile(),
+                        act = license.LicenseAction(ci.extractfile(),
                             license="%s.copyright" % self.pkgname)
                         act.hash = "install/copyright"
                 elif mapline.type == "i":
--- a/src/modules/bundle/SolarisPackageDirBundle.py	Wed Feb 29 11:26:51 2012 -0800
+++ b/src/modules/bundle/SolarisPackageDirBundle.py	Wed Feb 29 11:37:15 2012 -0800
@@ -21,7 +21,7 @@
 #
 
 #
-# Copyright (c) 2007, 2011, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2007, 2012, Oracle and/or its affiliates. All rights reserved.
 #
 
 import os
@@ -281,7 +281,7 @@
                                 name = name.replace("_", "-")
                                 legacy_attrs[name] = pkginfo[key]
 
-                actions.append(LegacyAction(data=None, **legacy_attrs))
+                actions.append(LegacyAction(**legacy_attrs))
 
                 if "DESC" in pkginfo:
                         actions.append(AttributeAction(name="pkg.description",
--- a/src/modules/catalog.py	Wed Feb 29 11:26:51 2012 -0800
+++ b/src/modules/catalog.py	Wed Feb 29 11:37:15 2012 -0800
@@ -516,8 +516,8 @@
 
                 for pub, stem, entry in self.__iter_entries(last=last,
                     ordered=ordered, pubs=pubs):
-                        f = fmri.PkgFmri("%s@%s" % (stem, entry["version"]),
-                            publisher=pub)
+                        f = fmri.PkgFmri(name=stem, publisher=pub,
+                            version=entry["version"])
                         if cb is None or cb(f, entry):
                                 yield f, entry
 
@@ -538,8 +538,8 @@
                         ver_list = self.__data[pub].get(name, ())
                         for entry in ver_list:
                                 sver = entry["version"]
-                                pfmri = fmri.PkgFmri("%s@%s" % (name,
-                                    sver), publisher=pub)
+                                pfmri = fmri.PkgFmri(name=name, publisher=pub,
+                                    version=sver)
 
                                 versions[sver] = pfmri.version
                                 entries.setdefault(sver, [])
@@ -576,8 +576,8 @@
                 if objects:
                         for pub, stem, entry in self.__iter_entries(last=last,
                             ordered=ordered, pubs=pubs):
-                                yield fmri.PkgFmri("%s@%s" % (stem,
-                                    entry["version"]), publisher=pub)
+                                yield fmri.PkgFmri(name=stem, publisher=pub,
+                                    version=entry["version"])
                         return
 
                 for pub, stem, entry in self.__iter_entries(last=last,
@@ -605,8 +605,8 @@
 
                         for entry in ver_list:
                                 sver = entry["version"]
-                                pfmri = fmri.PkgFmri("%s@%s" % (name,
-                                    sver), publisher=pub)
+                                pfmri = fmri.PkgFmri(name=name, publisher=pub,
+                                    version=sver)
 
                                 versions[sver] = pfmri.version
                                 entries.setdefault(sver, [])
@@ -1060,8 +1060,8 @@
                                 if key.startswith("catalog."):
                                         mdata[key] = entry[key]
                         op_time = basic_ts_to_datetime(entry["op-time"])
-                        pfmri = fmri.PkgFmri("%s@%s" % (stem, entry["version"]),
-                            publisher=pub)
+                        pfmri = fmri.PkgFmri(name=stem, publisher=pub,
+                            version=entry["version"])
                         return (pfmri, entry["op-type"], op_time, mdata)
 
                 for pub in self.publishers():
@@ -1368,14 +1368,15 @@
                 for f, entry in self.__entries(cb=cb, info_needed=info_needed,
                     locales=locales, last_version=last_version,
                     ordered=ordered, pubs=pubs):
-                        if "actions" in entry:
+                        try:
                                 yield f, self.__gen_actions(f, entry["actions"],
                                     excludes)
-                        elif self.__manifest_cb:
-                                yield f, self.__gen_lazy_actions(f, info_needed,
-                                    locales, excludes)
-                        else:
-                                yield f, EmptyI
+                        except KeyError:
+                                if self.__manifest_cb:
+                                        yield f, self.__gen_lazy_actions(f,
+                                            info_needed, locales, excludes)
+                                else:
+                                        yield f, EmptyI
 
         def __append(self, src, cb=None, pfmri=None, pubs=EmptyI):
                 """Private version; caller responsible for locking."""
@@ -1588,8 +1589,8 @@
                                 if not isinstance(pfmri, fmri.PkgFmri):
                                         # pfmri is assumed to be a FMRI tuple.
                                         pub, stem, ver = pfmri
-                                        pfmri = fmri.PkgFmri("%s@%s" % (stem,
-                                            ver), publisher=pub)
+                                        pfmri = fmri.PkgFmri(name=stem,
+                                            publisher=pub, version=ver)
                                 e.fmri = pfmri
                                 errors.append(e)
                                 continue
@@ -1800,7 +1801,8 @@
                                         npat = fmri.MatchingPkgFmri(pat_stem,
                                             brelease)
                                 else:
-                                        npat = fmri.PkgFmri(pat_stem, brelease)
+                                        npat = fmri.PkgFmri(pat_stem,
+                                            brelease)
 
                                 if not pat_ver:
                                         # Do nothing.
@@ -2554,19 +2556,20 @@
                 for r, entry in self.__entries(cb=cb, info_needed=info_needed,
                     locales=locales, last_version=last, ordered=ordered,
                     pubs=pubs, tuples=True):
-                        if "actions" in entry:
+                        try:
                                 yield (r, entry,
                                     self.__gen_actions(r, entry["actions"],
                                     excludes))
-                        elif self.__manifest_cb:
-                                pub, stem, ver = r
-                                f = fmri.PkgFmri("%s@%s" % (stem, ver),
-                                    publisher=pub)
-                                yield (r, entry,
-                                    self.__gen_lazy_actions(f, info_needed,
-                                    locales, excludes))
-                        else:
-                                yield r, entry, EmptyI
+                        except KeyError:
+                                if self.__manifest_cb:
+                                        pub, stem, ver = r
+                                        f = fmri.PkgFmri(name=stem, publisher=pub,
+                                            version=ver)
+                                        yield (r, entry,
+                                            self.__gen_lazy_actions(f, info_needed,
+                                            locales, excludes))
+                                else:
+                                        yield r, entry, EmptyI
 
         @property
         def exists(self):
@@ -2773,14 +2776,15 @@
                 if entry is None:
                         raise api_errors.UnknownCatalogEntry(pfmri.get_fmri())
 
-                if "actions" in entry:
+                try:
                         return self.__gen_actions(pfmri, entry["actions"],
                             excludes)
-                elif self.__manifest_cb:
-                        return self.__gen_lazy_actions(pfmri, info_needed,
-                            locales, excludes)
-                else:
-                        return EmptyI
+                except KeyError:
+                        if self.__manifest_cb:
+                                return self.__gen_lazy_actions(pfmri,
+                                    info_needed, locales, excludes)
+                        else:
+                                return EmptyI
 
         def get_entry_all_variants(self, pfmri):
                 """A generator function that yields tuples of the format
@@ -2793,13 +2797,14 @@
                 if entry is None:
                         raise api_errors.UnknownCatalogEntry(pfmri.get_fmri())
 
-                if "actions" in entry:
+                try:
                         actions = self.__gen_actions(pfmri, entry["actions"])
-                elif self.__manifest_cb:
-                        actions = self.__gen_lazy_actions(pfmri,
-                            info_needed)
-                else:
-                        return
+                except KeyError:
+                        if self.__manifest_cb:
+                                actions = self.__gen_lazy_actions(pfmri,
+                                    info_needed)
+                        else:
+                                return
 
                 for a in actions:
                         if a.name != "set":
@@ -3099,8 +3104,8 @@
 
                         # Return the requested package data.
                         if return_fmris:
-                                pfmri = fmri.PkgFmri("%s@%s" % (stem, ver),
-                                    build_release=brelease, publisher=pub)
+                                pfmri = fmri.PkgFmri(build_release=brelease,
+                                    name=stem, publisher=pub, version=ver)
                                 yield (pfmri, states, attrs)
                         else:
                                 yield (t, states, attrs)
@@ -3747,8 +3752,8 @@
                 base = self.get_part(self.__BASE_PART, must_exist=True)
                 if base is None:
                         if not pfmri:
-                                pfmri = fmri.PkgFmri("%s@%s" % (stem, ver),
-                                    publisher=pub)
+                                pfmri = fmri.PkgFmri(name=stem, publisher=pub,
+                                    version=ver)
                         raise api_errors.UnknownCatalogEntry(pfmri.get_fmri())
 
                 # get_entry returns the actual catalog entry, so updating it
@@ -3756,8 +3761,8 @@
                 entry = base.get_entry(pfmri=pfmri, pub=pub, stem=stem, ver=ver)
                 if entry is None:
                         if not pfmri:
-                                pfmri = fmri.PkgFmri("%s@%s" % (stem, ver),
-                                    publisher=pub)
+                                pfmri = fmri.PkgFmri(name=stem, publisher=pub,
+                                    version=ver)
                         raise api_errors.UnknownCatalogEntry(pfmri.get_fmri())
                 if metadata is None:
                         if "metadata" in entry:
@@ -3993,8 +3998,8 @@
                 else:
                         assert pattern != None
                         # XXX "5.11" here needs to be saner
-                        tuples[pattern] = \
-                            fmri.PkgFmri(pattern, "5.11").tuple()
+                        tuples[pattern] = fmri.PkgFmri(fmri=pattern,
+                            build_release="5.11").tuple()
 
         def by_pattern(p):
                 cat_pub, cat_name = p.tuple()[:2]
--- a/src/modules/client/api.py	Wed Feb 29 11:26:51 2012 -0800
+++ b/src/modules/client/api.py	Wed Feb 29 11:37:15 2012 -0800
@@ -3536,8 +3536,8 @@
                                 ranked_stems.setdefault(stem, pub)
 
                         if return_fmris:
-                                pfmri = fmri.PkgFmri("%s@%s" % (stem, ver),
-                                    build_release=brelease, publisher=pub)
+                                pfmri = fmri.PkgFmri(build_release=brelease,
+                                    name=stem, publisher=pub, version=ver)
                                 yield (pfmri, summ, pcats, states, attrs)
                         else:
                                 yield (t, summ, pcats, states, attrs)
--- a/src/modules/client/image.py	Wed Feb 29 11:26:51 2012 -0800
+++ b/src/modules/client/image.py	Wed Feb 29 11:37:15 2012 -0800
@@ -3125,8 +3125,8 @@
                                     pub=pub, stem=stem, ver=ver)
                                 nipart.add(metadata=entry, op_time=op_time,
                                     pub=pub, stem=stem, ver=ver)
-                                final_fmris.append(pkg.fmri.PkgFmri(
-                                    "%s@%s" % (stem, ver), publisher=pub))
+                                final_fmris.append(pkg.fmri.PkgFmri(name=stem,
+                                    publisher=pub, version=ver))
 
                 # Save the new catalogs.
                 for cat in kcat, icat:
--- a/src/modules/client/imageplan.py	Wed Feb 29 11:26:51 2012 -0800
+++ b/src/modules/client/imageplan.py	Wed Feb 29 11:37:15 2012 -0800
@@ -1102,19 +1102,22 @@
 
                 # Don't bother accounting for implicit directories if we're not
                 # looking for them.
-                if implicit_dirs and atype != "dir":
-                        implicit_dirs = False
-
+                if implicit_dirs:
+                        if atype != "dir":
+                                implicit_dirs = False
+                        else:
+                                da = pkg.actions.directory.DirectoryAction
+ 
                 for pfmri in generator():
                         m = self.image.get_manifest(pfmri, ignore_excludes=True)
-                        dirs = set() # Keep track of explicit dirs
+                        if implicit_dirs:
+                                dirs = set() # Keep track of explicit dirs
                         for act in m.gen_actions_by_type(atype,
                             self.__new_excludes):
                                 if implicit_dirs:
                                         dirs.add(act.attrs["path"])
                                 yield act, pfmri
                         if implicit_dirs:
-                                da = pkg.actions.directory.DirectoryAction
                                 for d in m.get_directories(self.__new_excludes):
                                         if d not in dirs:
                                                 yield da(path=d, implicit="true"), pfmri
@@ -1132,11 +1135,15 @@
 
                 # Don't bother accounting for implicit directories if we're not
                 # looking for them.
-                if implicit_dirs and atype != "dir":
-                        implicit_dirs = False
+                if implicit_dirs:
+                        if atype != "dir":
+                                implicit_dirs = False
+                        else:
+                                da = pkg.actions.directory.DirectoryAction
 
                 for pfmri, m in generator():
-                        dirs = set() # Keep track of explicit dirs
+                        if implicit_dirs:
+                                dirs = set() # Keep track of explicit dirs
                         for act in m.gen_actions_by_type(atype,
                             excludes):
                                 if implicit_dirs:
@@ -1144,7 +1151,6 @@
                                 yield act, pfmri
 
                         if implicit_dirs:
-                                da = pkg.actions.directory.DirectoryAction
                                 for d in m.get_directories(excludes):
                                         if d not in dirs:
                                                 yield da(path=d,
@@ -1154,15 +1160,19 @@
                 """ return set of all directories in target image """
                 # always consider var and the image directory fixed in image...
                 if self.__directories == None:
-                        dirs = set([self.image.imgdir.rstrip("/"),
-                                    "var",
-                                    "var/sadm",
-                                    "var/sadm/install"])
-                        dirs.update((
+                        # It's faster to build a large set and make a small
+                        # update to it than to do the reverse.
+                        dirs = set((
                             os.path.normpath(d[0].attrs["path"])
                             for d in self.gen_new_installed_actions_bytype("dir",
                                 implicit_dirs=True)
                         ))
+                        dirs.update([
+                            self.image.imgdir.rstrip("/"),
+                            "var",
+                            "var/sadm",
+                            "var/sadm/install"
+                        ])
                         self.__directories = dirs
                 return self.__directories
 
@@ -1533,10 +1543,10 @@
 
                 d = {}
                 for klass in action_classes:
+                        self.__progtrack.evaluate_progress()
                         for a, pfmri in \
                             gen_func(klass.name, implicit_dirs=True,
                             excludes=excludes):
-                                self.__progtrack.evaluate_progress()
                                 d.setdefault(a.attrs[klass.key_attr],
                                     []).append((a, pfmri))
                 return d
@@ -1706,7 +1716,7 @@
                         oactions = old.get(key, [])
                         # If new actions are being installed, then we need to do
                         # the full conflict checking.
-                        if len(oactions) == 0:
+                        if not oactions:
                                 continue
 
                         unmatched_old_actions = set(range(0, len(oactions)))
@@ -1714,8 +1724,9 @@
                         # If the action isn't refcountable and there's more than
                         # one action, that's an error so we let
                         # __check_conflicts handle it.
-                        if not actions[0][0].refcountable and \
-                            actions[0][0].globally_identical and \
+                        entry = actions[0][0]
+                        if not entry.refcountable and \
+                            entry.globally_identical and \
                             len(actions) > 1:
                                 continue
 
@@ -1724,16 +1735,18 @@
                         next_key = False
                         for act, pfmri in actions:
                                 matched = False
+                                aname = act.name
+                                aattrs = act.attrs
                                 # Compare this action with each outgoing action.
                                 for i, (oact, opfmri) in enumerate(oactions):
-                                        if act.name != oact.name:
+                                        if aname != oact.name:
                                                 continue
                                         # Check whether all attributes which
                                         # need to be unique are identical for
                                         # these two actions.
+                                        oattrs = oact.attrs
                                         if all((
-                                            act.attrs.get(a, None) ==
-                                                oact.attrs.get(a, None)
+                                            aattrs.get(a) == oattrs.get(a)
                                             for a in act.unique_attrs
                                         )):
                                                 matched = True
@@ -1759,8 +1772,8 @@
                                         if act.name != oact.name:
                                                 continue
                                         if all((
-                                            act.attrs.get(a, None) ==
-                                                oact.attrs.get(a, None)
+                                            act.attrs.get(a) ==
+                                                oact.attrs.get(a)
                                             for a in act.unique_attrs
                                         )):
                                                 matched = True
@@ -1817,8 +1830,8 @@
 
                         # Multiple non-refcountable actions delivered to
                         # the same name is an error.
-                        if not actions[0][0].refcountable and \
-                            actions[0][0].globally_identical:
+                        entry = actions[0][0]
+                        if not entry.refcountable and entry.globally_identical:
                                 if self.__process_conflicts(key,
                                     self.__check_duplicate_actions,
                                     actions, oactions,
@@ -1829,7 +1842,7 @@
                         # Multiple refcountable but globally unique
                         # actions delivered to the same name must be
                         # identical.
-                        elif actions[0][0].globally_identical:
+                        elif entry.globally_identical:
                                 if self.__process_conflicts(key,
                                     self.__check_inconsistent_attrs,
                                     actions, oactions,
@@ -3027,9 +3040,11 @@
                 self.update_actions.extend(l_refresh)
 
                 # sort actions to match needed processing order
-                self.removal_actions.sort(key = lambda obj:obj[1], reverse=True)
-                self.update_actions.sort(key = lambda obj:obj[2])
-                self.install_actions.sort(key = lambda obj:obj[2])
+                remsort = operator.itemgetter(1)
+                addsort = operator.itemgetter(2)
+                self.removal_actions.sort(key=remsort, reverse=True)
+                self.update_actions.sort(key=addsort)
+                self.install_actions.sort(key=addsort)
 
                 # Pre-calculate size of data retrieval for preexecute().
                 npkgs = nfiles = nbytes = 0
--- a/src/modules/client/pkg_solver.py	Wed Feb 29 11:26:51 2012 -0800
+++ b/src/modules/client/pkg_solver.py	Wed Feb 29 11:37:15 2012 -0800
@@ -36,6 +36,7 @@
 import pkg.version           as version
 
 from collections import defaultdict
+from itertools import chain
 from pkg.client.debugvalues import DebugValues
 from pkg.misc import EmptyI, EmptyDict, N_
 
@@ -96,9 +97,9 @@
                 self.__req_pkg_names = set()    # package names that must be
                                                 # present in solution by spec.
                 for f in self.__installed_fmris: # record only sticky pubs
-                        pub = f.get_publisher()
+                        pub = f.publisher
                         if self.__pub_ranks[pub][1]:
-                                self.__publisher[f.pkg_name] = f.get_publisher()
+                                self.__publisher[f.pkg_name] = pub
 
                 self.__id2fmri = {} 		# map ids -> fmris
                 self.__fmri2id = {} 		# and reverse
@@ -114,6 +115,7 @@
                 self.__variants = variants      # variants supported by image
 
                 self.__cache = {}
+                self.__depcache = {}
                 self.__trimdone = False         # indicate we're finished
                                                 # trimming
                 self.__fmri_state = {}          # cache of obsolete, renamed
@@ -125,7 +127,6 @@
                 self.__variables   = 0
                 self.__timings = []
                 self.__start_time = 0
-                self.__dep_dict = {}
                 self.__inc_list = []
                 self.__dependents = None
                 self.__root_fmris = None        # set of fmris installed in root image;
@@ -196,10 +197,10 @@
                 self.__variant_dict = None
                 self.__variants = None
                 self.__cache = None
+                self.__depcache = None
                 self.__trimdone = None
                 self.__fmri_state = None
                 self.__start_time = None
-                self.__dep_dict = None
                 self.__dependents = None
                 self.__fmridict = {}
 
@@ -293,7 +294,7 @@
                                 self.__mark_pub_trimmed(name)
                         else:
                                 self.__publisher[name] = \
-                                    proposed_dict[name][0].get_publisher()
+                                    proposed_dict[name][0].publisher
 
                 self.__removal_fmris |= set([
                     self.__installed_dict[name]
@@ -990,29 +991,120 @@
                 """Returns tuple of set of fmris that are matched within
                 CONSTRAINT.NONE of specified version and set of remaining
                 fmris."""
-                return self.__comb_common(fmri, dotrim,
-                    version.CONSTRAINT_NONE, obsolete_ok)
+
+                if dotrim or not obsolete_ok or not fmri.version or \
+                    not fmri.version.timestr:
+                        return self.__comb_common(fmri, dotrim,
+                            version.CONSTRAINT_NONE, obsolete_ok)
+
+                # In the case that a precise version (down to timestamp) is
+                # provided, no trimming is being done, and obsoletes are ok, the
+                # set of matching FMRIs can be determined without using version
+                # comparison because the solver caches catalog FMRIs in
+                # ascending version order.
+
+                # determine if the data is cacheable or cached:
+                tp = (fmri, dotrim, version.CONSTRAINT_NONE, obsolete_ok)
+                try:
+                        return self.__cache[tp]
+                except KeyError:
+                        pass
+
+                mver = fmri.version
+                all_fmris = self.__get_catalog_fmris(fmri.pkg_name)
+                all_fmris.reverse()
+
+                # frozensets are used so callers don't inadvertently
+                # update these sets (which may be cached).  Iteration is
+                # performed in descending version order with the
+                # assumption that systems are generally up-to-date so it
+                # should be faster to start at the end and look for the
+                # older version.
+                last_ver = None
+                for i, f in enumerate(all_fmris):
+                        if mver == f.version:
+                                last_ver = i
+                                continue
+                        elif last_ver is not None:
+                                break
+
+                if last_ver is not None:
+                        matching = frozenset(all_fmris[:last_ver + 1])
+                        remaining = frozenset(all_fmris[last_ver + 1:])
+                else:
+                        matching = frozenset()
+                        remaining = frozenset(all_fmris)
+
+                # if we haven't finished trimming, don't cache this
+                if not self.__trimdone:
+                        return matching, remaining
+
+                # cache the result
+                self.__cache[tp] = (matching, remaining)
+                return self.__cache[tp]
 
         def __comb_common(self, fmri, dotrim, constraint, obsolete_ok):
                 """Underlying impl. of other comb routines"""
+
                 tp = (fmri, dotrim, constraint, obsolete_ok) # cache index
                 # determine if the data is cacheable or cached:
                 if (not self.__trimdone and dotrim) or tp not in self.__cache:
-
                         # use frozensets so callers don't inadvertently update
                         # these sets (which may be cached).
-                        all_fmris = set(self.__get_catalog_fmris(fmri.pkg_name))
-                        matching = frozenset([
-                            f
-                            for f in all_fmris
-                            if f not in self.__trim_dict or not dotrim
-                            if not fmri.version or
-                                fmri.version == f.version or
-                                f.version.is_successor(fmri.version,
-                                    constraint=constraint)
-                            if obsolete_ok or not self.__fmri_is_obsolete(f)
-                        ])
-                        remaining = frozenset(all_fmris - matching)
+                        all_fmris = self.__get_catalog_fmris(fmri.pkg_name)
+                        mver = fmri.version
+                        if not mver:
+                                matching = frozenset((
+                                    f
+                                    for f in all_fmris
+                                    if not dotrim or f not in self.__trim_dict
+                                    if obsolete_ok or not self.__fmri_is_obsolete(f)
+                                ))
+                                remaining = frozenset(set(all_fmris) - matching)
+                        else:
+                                all_fmris.reverse()
+
+                                # Iteration is performed in descending version
+                                # order with the assumption that systems are
+                                # generally up-to-date so it should be faster to
+                                # start at the end and look for the oldest
+                                # version that matches.
+                                first_ver = None
+                                last_ver = None
+                                for i, f in enumerate(all_fmris):
+                                        fver = f.version
+                                        if (fver.is_successor(mver,
+                                            constraint=constraint) or \
+                                                fver == mver):
+                                                if first_ver is None:
+                                                        first_ver = i
+                                                last_ver = i
+                                        elif last_ver is not None:
+                                                break
+
+                                if last_ver is not None:
+                                        # Oddly enough, it's a little bit faster
+                                        # to iterate through the slice of
+                                        # all_fmris again and store matches here
+                                        # instead of above.  Perhaps variable
+                                        # scoping overhead is to blame?
+                                        matching = []
+                                        remaining = []
+                                        for f in all_fmris[first_ver:last_ver + 1]:
+                                                if ((not dotrim or
+                                                    f not in self.__trim_dict) and
+                                                    (obsolete_ok or not
+                                                        self.__fmri_is_obsolete(f))):
+                                                        matching.append(f)
+                                                else:
+                                                        remaining.append(f)
+                                        matching = frozenset(matching)
+                                        remaining = frozenset(chain(remaining,
+                                            all_fmris[:first_ver],
+                                            all_fmris[last_ver + 1:]))
+                                else:
+                                        matching = frozenset()
+                                        remaining = frozenset(all_fmris)
 
                         # if we haven't finished trimming, don't cache this
                         if not self.__trimdone:
@@ -1032,7 +1124,7 @@
                 else:
                         # we're going to return the older packages, so we need
                         # to make sure that any trimmed packages are removed
-                        # from the matching set and added to the nom-matching
+                        # from the matching set and added to the non-matching
                         # ones.
                         trimmed_older = set([
                                 f
@@ -1086,13 +1178,18 @@
                 """Return list of all dependency actions for this fmri"""
 
                 try:
-                        return [
+                        return self.__depcache[fmri]
+                except KeyError:
+                        pass
+
+                try:
+                        self.__depcache[fmri] = [
                             a
                             for a in self.__catalog.get_entry_actions(fmri,
                             [catalog.Catalog.DEPENDENCY], excludes=excludes)
                             if a.name == "depend"
                         ]
-
+                        return self.__depcache[fmri]
                 except api_errors.InvalidPackageErrors:
                         if trim_invalid:
                                 # Trim package entries that have unparseable
@@ -1831,7 +1928,7 @@
                 else:
                         # order by pub_rank; choose highest possible tier for
                         # pkgs; guard against unconfigured publishers in known catalog
-                        pubs_found = list(set([f.get_publisher() for f in fmri_list]))
+                        pubs_found = set((f.publisher for f in fmri_list))
                         ranked = sorted([
                                         (self.__pub_ranks[p][0], p)
                                         for p in pubs_found
@@ -1846,27 +1943,19 @@
                         else:
                                 reason = N_("Package publisher is ranked lower in search order")
 
-                # generate a dictionary, indexed by version, of acceptable fmris
-                for f in fmri_list:
-                        if f.get_publisher() in acceptable_pubs:
-                                version_dict.setdefault(f.version, []).append(f)
-
                 # allow installed packages to co-exist to meet dependency reqs.
                 # in case new publisher not proper superset of original.
                 # avoid multiple publishers w/ the exact same fmri to prevent
                 # thrashing in the solver due to many equiv. solutions.
-
-                inst_f = self.__installed_dict.get(pkg_name, None)
-
-                if inst_f:
-                        version_dict[inst_f.version] = [inst_f]
-
-                acceptable_list = []
-                for l in version_dict.values():
-                        acceptable_list.extend(l)
-
-                for f in set(fmri_list) - set(acceptable_list):
-                        self.__trim(f, reason)
+                inst_f = self.__installed_dict.get(pkg_name)
+                self.__trim((
+                    f
+                    for f in fmri_list
+                    if (f.publisher not in acceptable_pubs and
+                            (not inst_f or f != inst_f)) or
+                        (inst_f and f.publisher != inst_f.publisher and
+                            f.version == inst_f.version)
+                ), reason)
 
         # routines to manage the trim dictionary
         # trim dictionary contains the reasons an fmri was rejected for consideration
--- a/src/modules/facet.py	Wed Feb 29 11:26:51 2012 -0800
+++ b/src/modules/facet.py	Wed Feb 29 11:37:15 2012 -0800
@@ -26,9 +26,11 @@
 
 # basic facet support
 
+from pkg._varcet import _allow_facet
 from pkg.misc import EmptyI
 import fnmatch
 import re
+import types
 
 class Facets(dict):
         # store information on facets; subclass dict 
@@ -68,6 +70,9 @@
 
         def __getitem__(self, item):
                 """implement facet lookup algorithm here"""
+                # Note that _allow_facet bypasses __getitem__ for performance
+                # reasons; if __getitem__ changes, _allow_facet in _varcet.c
+                # must also be updated.
                 if not item.startswith("facet."):
                         raise KeyError, "key must start w/ facet."
 
@@ -84,6 +89,9 @@
                 self.__keylist.remove(item)
                 del self.__res[item]
 
+        # allow_action is provided as a native function (see end of class
+        # declaration).
+
         def pop(self, item, *args, **kwargs):
                 assert len(args) == 0 or (len(args) == 1 and
                     "default" not in kwargs)
@@ -134,18 +142,4 @@
                 self.__res = []
                 dict.clear(self)
 
-        def allow_action(self, action):
-                """ determine if facets permit this action; if any facets allow
-                it, return True; also return True if no facets are present"""
-
-                ret = True
-
-                for f in action.attrs.keys():
-                        if f[:6] != "facet.":
-                                continue
-                        if self[f]:
-                                return True
-                        else:
-                                ret = False
-
-                return ret
+Facets.allow_action = types.MethodType(_allow_facet, None, Facets)
--- a/src/modules/flavor/base.py	Wed Feb 29 11:26:51 2012 -0800
+++ b/src/modules/flavor/base.py	Wed Feb 29 11:37:15 2012 -0800
@@ -21,7 +21,7 @@
 #
 
 #
-# Copyright (c) 2009, 2011, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2009, 2012, Oracle and/or its affiliates. All rights reserved.
 #
 
 import os
@@ -121,6 +121,11 @@
                 ])
 
                 attrs.update(action.get_variant_template())
+                # Only lists are permitted for multi-value action attributes.
+                for k, v in attrs.items():
+                        if isinstance(v, set):
+                                attrs[k] = list(v)
+
                 depend.DependencyAction.__init__(self, **attrs)
 
         def is_error(self):
--- a/src/modules/fmri.py	Wed Feb 29 11:26:51 2012 -0800
+++ b/src/modules/fmri.py	Wed Feb 29 11:37:15 2012 -0800
@@ -114,26 +114,39 @@
 
         __slots__ = ["version", "publisher", "pkg_name", "_hash", "__weakref__"]
 
-        def __init__(self, fmri, build_release=None, publisher=None):
-                fmri = fmri.rstrip()
+        def __init__(self, fmri=None, build_release=None, publisher=None,
+            name=None, version=None):
+                if fmri is not None:
+                        fmri = fmri.rstrip()
 
-                veridx, nameidx, pubidx = PkgFmri._gen_fmri_indexes(fmri)
+                        veridx, nameidx, pubidx = \
+                            PkgFmri._gen_fmri_indexes(fmri)
+
+                        if pubidx != None:
+                                # Always use publisher information provided in
+                                # FMRI string.  (It could be ""; pkg:///name is
+                                # valid.)
+                                publisher = fmri[pubidx:nameidx - 1]
 
-                if veridx != None:
-                        try:
-                                self.version = Version(fmri[veridx + 1:],
-                                    build_release)
-                        except VersionError, iv:
-                                raise IllegalFmri(fmri, IllegalFmri.BAD_VERSION,
-                                    nested_exc=iv)
-
+                        if veridx != None:
+                                self.pkg_name = fmri[nameidx:veridx]
+                                try:
+                                        self.version = Version(
+                                            fmri[veridx + 1:], build_release)
+                                except VersionError, iv:
+                                        raise IllegalFmri(fmri,
+                                            IllegalFmri.BAD_VERSION,
+                                            nested_exc=iv)
+                        else:
+                                self.pkg_name = fmri[nameidx:]
+                                self.version = None
                 else:
-                        self.version = veridx = None
-
-                if pubidx != None:
-                        # Always use publisher information provided in FMRI
-                        # string.  (It could be ""; pkg:///name is valid.)
-                        publisher = fmri[pubidx:nameidx - 1]
+                        # pkg_name and version must be explicitly set.
+                        self.pkg_name = name
+                        if version:
+                                self.version = Version(version, build_release)
+                        else:
+                                self.version = None
 
                 # Ensure publisher is always None if one was not specified.
                 if publisher:
@@ -141,11 +154,6 @@
                 else:
                         self.publisher = None
 
-                if veridx != None:
-                        self.pkg_name = fmri[nameidx:veridx]
-                else:
-                        self.pkg_name = fmri[nameidx:]
-
                 if not self.pkg_name:
                         raise IllegalFmri(fmri, IllegalFmri.SYNTAX_ERROR,
                             detail="Missing package name")
--- a/src/modules/lint/pkglint_action.py	Wed Feb 29 11:26:51 2012 -0800
+++ b/src/modules/lint/pkglint_action.py	Wed Feb 29 11:37:15 2012 -0800
@@ -21,7 +21,7 @@
 #
 
 #
-# Copyright (c) 2010, 2011, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2010, 2012, Oracle and/or its affiliates. All rights reserved.
 #
 
 from pkg.lint.engine import lint_fmri_successor
@@ -129,7 +129,12 @@
 
                                 variants = action.get_variant_template()
                                 variants.merge_unknown(pkg_vars)
-                                action.attrs.update(variants)
+                                # Action attributes must be lists or strings.
+                                for k, v in variants.iteritems():
+                                        if isinstance(v, set):
+                                                action.attrs[k] = list(v)
+                                        else:
+                                                action.attrs[k] = v
 
                                 p = action.attrs[attr]
                                 if p not in dic:
--- a/src/modules/manifest.py	Wed Feb 29 11:26:51 2012 -0800
+++ b/src/modules/manifest.py	Wed Feb 29 11:37:15 2012 -0800
@@ -29,7 +29,8 @@
 import hashlib
 import os
 import tempfile
-from itertools import groupby, chain, repeat
+from itertools import groupby, chain, repeat, izip
+from operator import itemgetter
 
 import pkg.actions as actions
 import pkg.client.api_errors as apx
@@ -83,13 +84,13 @@
         def __init__(self, pfmri=None):
                 self.fmri = pfmri
 
+                self._cache = {}
+                self._facets = None     # facets seen in package
+                self._variants = None   # variants seen in package
                 self.actions = []
                 self.actions_bytype = {}
-                self.variants = {}   # variants seen in package
-                self.facets = {}     # facets seen in package
                 self.attributes = {} # package-wide attributes
                 self.signatures = EmptyDict
-                self._cache = {}
                 self.excludes = EmptyI
 
         def __str__(self):
@@ -136,10 +137,9 @@
 
                 def hashify(v):
                         """handle key values that may be lists"""
-                        if isinstance(v, list):
-                                return frozenset(v)
-                        else:
+                        if type(v) is not list:
                                 return v
+                        return frozenset(v)
 
                 sdict = dict(
                     ((a.name, hashify(a.attrs.get(a.key_attr, id(a)))), a)
@@ -150,8 +150,8 @@
                     for a in origin.gen_actions(origin_exclude)
                 )
 
-                sset = set(sdict.keys())
-                oset = set(odict.keys())
+                sset = set(sdict.iterkeys())
+                oset = set(odict.iterkeys())
 
                 added = [(None, sdict[i]) for i in sset - oset]
                 removed = [(odict[i], None) for i in oset - sset]
@@ -167,11 +167,11 @@
                 # sorted list?
 
                 # singlesort = lambda x: x[0] or x[1]
-                addsort = lambda x: x[1]
-                remsort = lambda x: x[0]
-                removed.sort(key = remsort, reverse = True)
-                added.sort(key = addsort)
-                changed.sort(key = addsort)
+                addsort = itemgetter(1)
+                remsort = itemgetter(0)
+                removed.sort(key=remsort, reverse=True)
+                added.sort(key=addsort)
+                changed.sort(key=addsort)
 
                 return ManifestDifference(added, changed, removed)
 
@@ -358,14 +358,14 @@
                 except KeyError:
                         # generate actions that contain directories
                         alist = self._cache["manifest.dircache"] = [
-                            actions.fromstr(s.strip())
+                            actions.fromstr(s.rstrip())
                             for s in self._gen_dirs_to_str()
                         ]
 
                 s = set([
                     a.attrs["path"]
                     for a in alist
-                    if a.include_this(excludes)
+                    if not excludes or a.include_this(excludes)
                 ])
 
                 return list(s)
@@ -386,7 +386,7 @@
                 except KeyError:
                         # generate actions that contain mediators
                         alist = self._cache["manifest.mediatorcache"] = [
-                            actions.fromstr(s.strip())
+                            actions.fromstr(s.rstrip())
                             for s in self._gen_mediators_to_str()
                         ]
 
@@ -394,7 +394,7 @@
                 for attrs in (
                     act.attrs
                     for act in alist
-                    if act.include_this(excludes)):
+                    if not excludes or act.include_this(excludes)):
                         med_ver = attrs.get("mediator-version")
                         if med_ver:
                                 try:
@@ -543,8 +543,8 @@
 
                 self.actions = []
                 self.actions_bytype = {}
-                self.variants = {}
-                self.facets = {}
+                self._variants = None
+                self._facets = None
                 self.attributes = {}
                 self._cache = {}
 
@@ -594,45 +594,39 @@
                 The "excludes" parameter is the variants to exclude from the
                 manifest."""
 
-                # XXX handle legacy transition issues; not needed after
-                # 2009.06 release & republication are complete.
-                if "opensolaris.zone" in action.attrs and \
-                    "variant.opensolaris.zone" not in action.attrs:
-                        action.attrs["variant.opensolaris.zone"] = \
-                            action.attrs["opensolaris.zone"]
+                attrs = action.attrs
+                aname = action.name
 
-                if action.name == "set" and action.attrs["name"] == "authority":
+                # XXX handle legacy transition issues; not needed once support
+                # for upgrading images from older releases (< build 151) has
+                # been removed.
+                if "opensolaris.zone" in attrs and \
+                    "variant.opensolaris.zone" not in attrs:
+                        attrs["variant.opensolaris.zone"] = \
+                            attrs["opensolaris.zone"]
+
+                if aname == "set" and attrs["name"] == "authority":
                         # Translate old action to new.
-                        action.attrs["name"] = "publisher"
+                        attrs["name"] = "publisher"
+
+                if excludes and not action.include_this(excludes):
+                        return
 
-                if action.attrs.has_key("path"):
-                        np = action.attrs["path"].lstrip(os.path.sep)
-                        action.attrs["path"] = np
-
-                if not action.include_this(excludes):
-                        return
+                if self._variants:
+                        # Reset facet/variant cache if needed (if one is set,
+                        # then both are set, so only need to check for one).
+                        self._facets = None
+                        self._variants = None
 
                 self.actions.append(action)
-                self.actions_bytype.setdefault(action.name, []).append(action)
+                try:
+                        self.actions_bytype[aname].append(action)
+                except KeyError:
+                        self.actions_bytype.setdefault(aname, []).append(action)
 
                 # add any set actions to attributes
-                if action.name == "set":
+                if aname == "set":
                         self.fill_attributes(action)
-                # append any variants and facets to manifest dict
-                v_list, f_list = action.get_varcet_keys()
-
-                if not (v_list or f_list):
-                        return
-
-                try:
-                        for v, d in zip(v_list, repeat(self.variants)) \
-                            + zip(f_list, repeat(self.facets)):
-                                d.setdefault(v, set()).add(action.attrs[v])
-                except TypeError:
-                        # Lists can't be set elements.
-                        raise actions.InvalidActionError(action,
-                            _("%(forv)s '%(v)s' specified multiple times") %
-                            {"forv": v.split(".", 1)[0], "v": v})
 
         def fill_attributes(self, action):
                 """Fill attribute array w/ set action contents."""
@@ -710,7 +704,8 @@
                                         log((_("%(fp)s:\n%(e)s") %
                                             { "fp": file_path, "e": e }))
                                 else:
-                                        if action.include_this(excludes):
+                                        if not excludes or \
+                                            action.include_this(excludes):
                                                 if action.attrs.has_key("path"):
                                                         np = action.attrs["path"].lstrip(os.path.sep)
                                                         action.attrs["path"] = \
@@ -857,6 +852,44 @@
                         size += a.get_size()
                 return size
 
+        def __load_varcets(self):
+                """Private helper function to populate list of facets and
+                variants on-demand."""
+
+                self._facets = {}
+                self._variants = {}
+                for action in self.gen_actions():
+                        # append any variants and facets to manifest dict
+                        attrs = action.attrs
+                        v_list, f_list = action.get_varcet_keys()
+
+                        if not (v_list or f_list):
+                                continue
+
+                        try:
+                                for v, d in chain(
+                                    izip(v_list, repeat(self._variants)),
+                                    izip(f_list, repeat(self._facets))):
+                                        try:
+                                                d[v].add(attrs[v])
+                                        except KeyError:
+                                                d[v] = set([attrs[v]])
+                        except TypeError:
+                                # Lists can't be set elements.
+                                raise actions.InvalidActionError(action,
+                                    _("%(forv)s '%(v)s' specified multiple times") %
+                                    {"forv": v.split(".", 1)[0], "v": v})
+
+        def __get_facets(self):
+                if self._facets is None:
+                        self.__load_varcets()
+                return self._facets
+
+        def __get_variants(self):
+                if self._variants is None:
+                        self.__load_varcets()
+                return self._variants
+
         def __getitem__(self, key):
                 """Return the value for the package attribute 'key'."""
                 return self.attributes[key]
@@ -876,6 +909,9 @@
         def __contains__(self, key):
                 return key in self.attributes
 
+        facets = property(lambda self: self.__get_facets())
+        variants = property(lambda self: self.__get_variants())
+
 null = Manifest()
 
 class FactoredManifest(Manifest):
@@ -964,8 +1000,8 @@
                 when downloading new manifests"""
                 self.actions = []
                 self.actions_bytype = {}
-                self.variants = {}
-                self.facets = {}
+                self._variants = None
+                self._facets = None
                 self.attributes = {}
                 self.loaded = False
 
@@ -1070,10 +1106,11 @@
                                 self._cache[name] = [
                                     a for a in
                                     (
-                                        actions.fromstr(s.strip())
+                                        actions.fromstr(s.rstrip())
                                         for s in f
                                     )
-                                    if a.include_this(self.excludes)
+                                    if not self.excludes or
+                                        a.include_this(self.excludes)
                                 ]
                 except EnvironmentError, e:
                         raise apx._convert_error(e)
@@ -1122,12 +1159,12 @@
                         if not os.path.exists(mpath):
                                 return # no such action in this manifest
 
-                        f = file(mpath)
-                        for l in f:
-                                a = actions.fromstr(l.strip())
-                                if a.include_this(excludes):
-                                        yield a
-                        f.close()
+                        with open(mpath, "rb") as f:
+                                for l in f:
+                                        a = actions.fromstr(l.rstrip())
+                                        if not excludes or \
+                                            a.include_this(excludes):
+                                                yield a
 
         def gen_mediators(self, excludes):
                 """A generator function that yields set actions expressing the
@@ -1143,12 +1180,12 @@
                 mpath = self.__cache_path("manifest.set")
                 if not os.path.exists(mpath):
                         return False
-                f = file(mpath)
-                for l in f:
-                        a = actions.fromstr(l.strip())
-                        if a.include_this(self.excludes):
-                                self.fill_attributes(a)
-                f.close()
+                with open(mpath, "rb") as f:
+                        for l in f:
+                                a = actions.fromstr(l.rstrip())
+                                if not self.excludes or \
+                                    a.include_this(self.excludes):
+                                        self.fill_attributes(a)
                 return True
 
         def __getitem__(self, key):
--- a/src/modules/p5p.py	Wed Feb 29 11:26:51 2012 -0800
+++ b/src/modules/p5p.py	Wed Feb 29 11:37:15 2012 -0800
@@ -21,7 +21,7 @@
 #
 
 #
-# Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2011, 2012, Oracle and/or its affiliates. All rights reserved.
 #
 
 import atexit
@@ -798,8 +798,8 @@
                                 ignored, stem, ver = name.rsplit("/", 2)
                                 stem = urllib.unquote(stem)
                                 ver = urllib.unquote(ver)
-                                pfmri = pkg.fmri.PkgFmri("%s@%s" % (stem, ver),
-                                    publisher=pub)
+                                pfmri = pkg.fmri.PkgFmri(name=stem,
+                                    publisher=pub, version=ver)
 
                                 fobj = self.get_file(name)
                                 m = pkg.manifest.Manifest(pfmri=pfmri)
--- a/src/modules/publish/dependencies.py	Wed Feb 29 11:26:51 2012 -0800
+++ b/src/modules/publish/dependencies.py	Wed Feb 29 11:37:15 2012 -0800
@@ -21,7 +21,7 @@
 #
 
 #
-# Copyright (c) 2009, 2011, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2009, 2012, Oracle and/or its affiliates. All rights reserved.
 #
 
 import copy
@@ -267,15 +267,25 @@
         """Convert pkg.base.Dependency objects to
         pkg.actions.dependency.Dependency objects."""
 
+        def norm_attrs(a):
+                """Normalize attribute values as lists instead of sets; only
+                lists are permitted for multi-value action attributes."""
+                for k, v in a.items():
+                        if isinstance(v, set):
+                                a[k] = list(v)
+
         res = []
         for d in deps:
                 tmp = []
                 for c in d.dep_vars.not_sat_set:
                         attrs = d.attrs.copy()
                         attrs.update(c)
+                        norm_attrs(attrs)
                         tmp.append(actions.depend.DependencyAction(**attrs))
                 if not tmp:
-                        tmp.append(actions.depend.DependencyAction(**d.attrs))
+                        attrs = d.attrs.copy()
+                        norm_attrs(attrs)
+                        tmp.append(actions.depend.DependencyAction(**attrs))
                 res.extend(tmp)
         return res
 
--- a/src/modules/variant.py	Wed Feb 29 11:26:51 2012 -0800
+++ b/src/modules/variant.py	Wed Feb 29 11:37:15 2012 -0800
@@ -21,15 +21,17 @@
 #
 
 #
-# Copyright (c) 2009, 2011, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2009, 2012, Oracle and/or its affiliates. All rights reserved.
 #
 
 # basic variant support
 
 import copy
 import itertools
+import types
+
 from collections import namedtuple
-
+from pkg._varcet import _allow_variant
 from pkg.misc import EmptyI
 
 class Variants(dict):
@@ -50,6 +52,9 @@
                 dict.__delitem__(self, item)
                 self.__keyset.remove(item)
 
+        # allow_action is provided as a native function (see end of class
+        # declaration).
+
         def pop(self, item, default=None):
                 self.__keyset.discard(item)
                 return dict.pop(self, item, default)
@@ -75,25 +80,8 @@
                 self.__keyset = set()
                 dict.clear(self)
 
-        # Methods which are unique to variants
-        def allow_action(self, action):
-                """ determine if variants permit this action """
-
-                for k, v in action.attrs.iteritems():
-                        if k[:8] != "variant.":
-                                continue
-                        sys_v = self.get(k, None)
+Variants.allow_action = types.MethodType(_allow_variant, None, Variants)
 
-                        # If the system value and the action variant value
-                        # agree, then the action is allowed.  Otherwise, if the
-                        # system value doesn't exist, check to see if the
-                        # action's variant is a debug variant.  If it is, allow
-                        # the action if the variant value is set to false.
-                        if sys_v != v and (sys_v is not None or
-                            (k[:14] == "variant.debug." and v != "false")):
-                                return False
-
-                return True
 
 # The two classes which follow are used during dependency calculation when
 # actions have variants, or the packages they're contained in do.  The
--- a/src/modules/version.py	Wed Feb 29 11:26:51 2012 -0800
+++ b/src/modules/version.py	Wed Feb 29 11:37:15 2012 -0800
@@ -21,7 +21,7 @@
 #
 
 #
-# Copyright (c) 2007, 2011, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2007, 2012, Oracle and/or its affiliates. All rights reserved.
 #
 
 import calendar
@@ -29,6 +29,8 @@
 import time
 import weakref
 
+from itertools import izip
+
 CONSTRAINT_NONE = 0
 CONSTRAINT_AUTO = 50
 
@@ -76,12 +78,10 @@
                 return x
 
         def __new__(cls, dotstring):
-                ds = DotSequence.__dotseq_pool.get(dotstring, None)
-                if ds is not None:
-                        return ds
-
-                ds = list.__new__(cls)
-                cls.__dotseq_pool[dotstring] = ds
+                ds = DotSequence.__dotseq_pool.get(dotstring)
+                if ds is None:
+                        cls.__dotseq_pool[dotstring] = ds = \
+                            list.__new__(cls)
                 return ds
 
         def __init__(self, dotstring):
@@ -113,7 +113,7 @@
                 if len(self) > len(other):
                         return False
 
-                for a, b in zip(self, other):
+                for a, b in izip(self, other):
                         if a != b:
                                 return False
                 return True
@@ -219,7 +219,7 @@
                 if len(self) > len(other):
                         return False
 
-                for a, b in zip(self, other):
+                for a, b in izip(self, other):
                         if a != b:
                                 return False
                 return True
--- a/src/pkg/manifests/package:pkg.p5m	Wed Feb 29 11:26:51 2012 -0800
+++ b/src/pkg/manifests/package:pkg.p5m	Wed Feb 29 11:37:15 2012 -0800
@@ -18,7 +18,7 @@
 #
 # CDDL HEADER END
 #
-# Copyright (c) 2010, 2011, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2010, 2012, Oracle and/or its affiliates. All rights reserved.
 #
 
 set name=pkg.fmri value=pkg:/package/pkg@$(PKGVERS)
@@ -36,9 +36,11 @@
 dir  path=$(PYDIRVP)/pkg
 file path=$(PYDIRVP)/pkg-0.1-py2.6.egg-info
 file path=$(PYDIRVP)/pkg/__init__.py
+file path=$(PYDIRVP)/pkg/_varcet.so
 dir  path=$(PYDIRVP)/pkg/actions
 file path=$(PYDIRVP)/pkg/actions/__init__.py
 file path=$(PYDIRVP)/pkg/actions/_actions.so
+file path=$(PYDIRVP)/pkg/actions/_common.so
 file path=$(PYDIRVP)/pkg/actions/attribute.py
 file path=$(PYDIRVP)/pkg/actions/depend.py
 file path=$(PYDIRVP)/pkg/actions/directory.py
--- a/src/setup.py	Wed Feb 29 11:26:51 2012 -0800
+++ b/src/setup.py	Wed Feb 29 11:37:15 2012 -0800
@@ -19,7 +19,7 @@
 #
 # CDDL HEADER END
 #
-# Copyright (c) 2008, 2011, Oracle and/or its affiliates.  All rights reserved.
+# Copyright (c) 2008, 2012, Oracle and/or its affiliates. All rights reserved.
 #
 
 import errno
@@ -422,6 +422,12 @@
 _actions_srcs = [
         'modules/actions/_actions.c'
         ]
+_actcomm_srcs = [
+        'modules/actions/_common.c'
+        ]
+_varcet_srcs = [
+        'modules/_varcet.c'
+        ]
 solver_srcs = [
         'modules/solver/solver.c',
         'modules/solver/py_solver.c'
@@ -516,6 +522,14 @@
                             ["%s%s" % ("-I", k) for k in include_dirs] + \
                             ['-I' + self.escape(get_python_inc())] + \
                             _actions_srcs
+                        _actcommcmd = ['lint'] + lint_flags + \
+                            ["%s%s" % ("-I", k) for k in include_dirs] + \
+                            ['-I' + self.escape(get_python_inc())] + \
+                            _actcomm_srcs
+                        _varcetcmd = ['lint'] + lint_flags + \
+                            ["%s%s" % ("-I", k) for k in include_dirs] + \
+                            ['-I' + self.escape(get_python_inc())] + \
+                            _varcet_srcs
                         pspawncmd = ['lint'] + lint_flags + ['-D_FILE_OFFSET_BITS=64'] + \
                             ["%s%s" % ("-I", k) for k in include_dirs] + \
                             ['-I' + self.escape(get_python_inc())] + \
@@ -531,6 +545,10 @@
                         os.system(" ".join(elfcmd))
                         print(" ".join(_actionscmd))
                         os.system(" ".join(_actionscmd))
+                        print(" ".join(_actcommcmd))
+                        os.system(" ".join(_actcommcmd))
+                        print(" ".join(_varcetcmd))
+                        os.system(" ".join(_varcetcmd))
                         print(" ".join(pspawncmd))
                         os.system(" ".join(pspawncmd))
                         print(" ".join(syscallatcmd))
@@ -1163,6 +1181,20 @@
                 extra_link_args = link_args
                 ),
         Extension(
+                'actions._common',
+                _actcomm_srcs,
+                include_dirs = include_dirs,
+                extra_compile_args = compile_args,
+                extra_link_args = link_args
+                ),
+        Extension(
+                '_varcet',
+                _varcet_srcs,
+                include_dirs = include_dirs,
+                extra_compile_args = compile_args,
+                extra_link_args = link_args
+                ),
+        Extension(
                 'solver',
                 solver_srcs,
                 include_dirs = include_dirs + ["."],
--- a/src/tests/api/t_action.py	Wed Feb 29 11:26:51 2012 -0800
+++ b/src/tests/api/t_action.py	Wed Feb 29 11:37:15 2012 -0800
@@ -21,7 +21,7 @@
 #
 
 #
-# Copyright (c) 2008, 2011, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2008, 2012, Oracle and/or its affiliates. All rights reserved.
 #
 
 import testutils
@@ -132,6 +132,17 @@
 
                 action.fromstr("signature 12345 algorithm=foo")
 
+                # For convenience, we allow set actions to be expressed as
+                # "<name>=<value>", rather than "name=<name> value=<value>", but
+                # we always convert to the latter.  Verify that both forms are
+                # parsed as expected.
+                a = action.fromstr("set pkg.obsolete=true")
+                a2 = action.fromstr("set name=pkg.obsolete value=true")
+                self.assertEqual(str(a), str(a2))
+                self.assertAttributes(a, ["name", "value"])
+                self.assertAttributeValue(a, "name", "pkg.obsolete")
+                self.assertAttributeValue(a, "value", "true")
+
                 # Single quoted value
                 a = action.fromstr("file 12345 name='foo' value=bar path=/tmp/foo")
                 self.assertAttributes(a, ["name", "value", "path"])
@@ -347,9 +358,12 @@
                 # Unknown action type
                 self.assertRaises(action.UnknownActionError, action.fromstr,
                     "moop bar=baz")
+                self.assertRaises(action.UnknownActionError, action.fromstr,
+                    "setbar=baz quux=quark")
 
-                # Nothing but the action type
+                # Nothing but the action type or type is malformed.
                 self.assertMalformed("moop")
+                self.assertMalformed("setbar=baz")
 
                 # Bad quoting: missing close quote
                 self.assertMalformed("file 12345 path=/tmp/foo name=\"foo bar")
@@ -457,7 +471,7 @@
                 # Verify multiple values for file attributes are rejected.
                 for attr in ("pkg.size", "pkg.csize", "chash", "preserve",
                     "overlay", "elfhash", "original_name", "facet.doc",
-                    "variant.count", "owner", "group"):
+                    "owner", "group"):
                         nact = "file path=/usr/bin/foo owner=root group=root " \
                             "mode=0555 %(attr)s=1 %(attr)s=2 %(attr)s=3" % {
                             "attr": attr }
--- a/src/tests/api/t_manifest.py	Wed Feb 29 11:26:51 2012 -0800
+++ b/src/tests/api/t_manifest.py	Wed Feb 29 11:37:15 2012 -0800
@@ -101,11 +101,6 @@
 file fff555ff9 mode=0555 owner=sch group=staff path=/usr/bin/i386/sort isa=i386
 """
 
-                self.m6_contents = """\
-dir owner=root path=usr/bin group=bin mode=0755 variant.opensolaris.zone=global variant.opensolaris.zone=nonglobal
-"""
-
-
         def test_set_content1(self):
                 """ ASSERT: manifest string repr reflects its construction """
 
@@ -140,13 +135,6 @@
                 self.assertEqual(bstr, output)
                 self.assert_(isinstance(output, str))
 
-        def test_variants(self):
-                """Make sure we reject multiple instances of the same
-                variant."""
-
-                self.assertRaises(actions.InvalidActionError,
-                    self.m1.set_content, self.m6_contents)
-
         def test_diffs1(self):
                 """ humanized_differences runs to completion """
 
--- a/src/tests/cli/t_pkgdep.py	Wed Feb 29 11:26:51 2012 -0800
+++ b/src/tests/cli/t_pkgdep.py	Wed Feb 29 11:37:15 2012 -0800
@@ -20,7 +20,7 @@
 # CDDL HEADER END
 #
 
-# Copyright (c) 2009, 2011, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2009, 2012, Oracle and/or its affiliates. All rights reserved.
 
 import testutils
 if __name__ == "__main__":
@@ -1414,18 +1414,19 @@
                 """ pkgdep should gracefully deal with a non-manifest """
 
                 m_path = None
+                atype = "This"
                 nonsense = "This is a nonsense manifest"
                 m_path = self.make_manifest(nonsense)
 
                 self.pkgdepend_generate("-d %s %s" %
                     (self.test_proto_dir, m_path), exit=1)
                 self.debug(self.errout)
-                self.check_res("Malformed action at position: 9:\n" + nonsense +
-                    "\n             ^\n", self.errout)
+                self.check_res("unknown action type '%s' in action '%s'" %
+                    (atype, nonsense), self.errout)
 
                 self.pkgdepend_resolve("-o %s " % m_path, exit=1)
-                self.check_res("Malformed action at position: 9:\n" + nonsense +
-                    "\n             ^\n", self.errout)
+                self.check_res("unknown action type '%s' in action '%s'" %
+                    (atype, nonsense), self.errout)
 
         def __run_dyn_tok_test(self, run_paths, replaced_path, dep_args):
                 """Using the provided run paths, produces a elf binary with
--- a/src/tests/perf/actionbench.py	Wed Feb 29 11:26:51 2012 -0800
+++ b/src/tests/perf/actionbench.py	Wed Feb 29 11:37:15 2012 -0800
@@ -21,7 +21,7 @@
 #
 
 #
-# Copyright (c) 2008, 2011, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2008, 2012, Oracle and/or its affiliates. All rights reserved.
 #
 
 #
@@ -75,15 +75,15 @@
         setup3 = """
 class superc(object):
         def __cmp__(a, b):
-                return cmp(a.ord, b.ord)
+                return cmp(a.ordinality, b.ordinality)
 
 class aa(superc):
         def __init__(self):
-                self.ord = 1    
+                self.ordinality = 1    
                 
 class bb(superc):
         def __init__(self):
-                self.ord = 2
+                self.ordinality = 2
                 
 a = aa()
 b = bb()
--- a/src/tests/perf/fmribench.py	Wed Feb 29 11:26:51 2012 -0800
+++ b/src/tests/perf/fmribench.py	Wed Feb 29 11:37:15 2012 -0800
@@ -20,9 +20,9 @@
 # CDDL HEADER END
 #
 
+
 #
-# Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
-# Use is subject to license terms.
+# Copyright (c) 2008, 2012, Oracle and/or its affiliates. All rights reserved.
 #
 
 #
@@ -94,11 +94,16 @@
         """hash(f1)"""
         ],
 
-        [ "fmri create", 50000,
+        [ "fmri create (string)", 50000,
         """import pkg.fmri as fmri""",
         """f = fmri.PkgFmri("pkg://origin/[email protected],5.11-0.72:20070921T203926Z")"""
         ],
 
+        [ "fmri create (parts)", 50000,
+        """import pkg.fmri as fmri""",
+        """f = fmri.PkgFmri(build_release="5.11", publisher="origin", name="SUNWxwssu", version="0.5.11,5.11-0.72:20070921T203926Z")"""
+        ],
+
         [ "fmri create (no tstamp)", 50000,
         """import pkg.fmri as fmri""",
         """f = fmri.PkgFmri("pkg://origin/[email protected],0.5.11-0.90")"""
--- a/src/util/publish/pkgdiff.py	Wed Feb 29 11:26:51 2012 -0800
+++ b/src/util/publish/pkgdiff.py	Wed Feb 29 11:37:15 2012 -0800
@@ -21,7 +21,7 @@
 #
 
 #
-# Copyright (c) 2009, 2011, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2009, 2012, Oracle and/or its affiliates. All rights reserved.
 #
 
 import getopt
@@ -204,7 +204,7 @@
                         if res:
                                 return res
                 else:
-                        res = cmp(a.ord, b.ord)
+                        res = cmp(a.ordinality, b.ordinality)
                         if res:
                                 return res
                 return cmp(str(a), str(b))