usr/src/cmd/auto-install/auto_ddu_lib.c
author Keith Mitchell <keith.mitchell@oracle.com>
Mon, 30 Aug 2010 15:52:14 -0700
changeset 869 424db5e50fb5
parent 867 cfbbedf29419
child 870 1cd031050724
permissions -rw-r--r--
GK: Backed out changeset cfbbedf29419 for build 147

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

#include "Python.h"	/* Must be the first header file. */
#include <alloca.h>
#include <libintl.h>
#include <strings.h>
#include <auto_install.h>

/* Python DDU function module related definitions. */
#define	DDU_FUNCTION_MODULE	"DDU.ddu_function"
#define	DDU_PACKAGE_LOOKUP	"ddu_package_lookup"
#define	DDU_INSTALL_PACKAGE	"ddu_install_package"
#define	DDU_DEVSCAN		"ddu_devscan"
#define	DDU_BUILD_REPO_LIST	"ddu_build_repo_list"

/* Python DDU package module related definitions. */
#define	DDU_PACKAGE_MODULE	"DDU.ddu_package"
#define	DDU_PACKAGE_OBJECT	"ddu_package_object"

/* DDU error log */
#define	DDU_ERRLOG		"/tmp/ddu_err.log"

/* ICT module related definitions. */
#define	ICT_MODULE		"osol_install.ict"
#define	ICT_CLASS		"ICT"
#define	ICT_UPDATE_ARCHIVE	"update_boot_archive"

/* AI Manifest (AIM) related path definitions. */
#define	AIM_PREFACE		    "auto_install/ai_instance/add_drivers/"
#define	PKGSPEC_NODEPATH	"software"
#define	ORIGIN_NODEPATH		"software/source/publisher/origin/name"
#define	TYPE_NODEPATH \
	"software[source/publisher/origin/name=\"%s\"]/software_data/type"
#define	NAME_NODEPATH \
	"software[source/publisher/origin/name=\"%s\":software_data/type=\"%s\"]/software_data/name"
#define	ACTION_NONAME_NODEPATH \
	"software[source/publisher/origin/name=\"%s\":software_data/type=\"%s\"]/software_data/action"
#define	ACTION_YESNAME_NODEPATH \
	"software[source/publisher/origin/name=\"%s\":software_data/type=\"%s\":software_data/name=\"%s\"]/software_data/action"

#define	SEARCH_NODEPATH		"search_all"
#define	SEARCH_ORIGIN_NODEPATH	"search_all/source/publisher/origin/name"
#define	SEARCH_PUBNAME_NODEPATH	"search_all/source/publisher/name"
#define	SEARCH_ADDALL_NODEPATH	"search_all/addall"

#define	MAX_NODEPATH_SIZE	256

typedef struct {
	PyThreadState *myThreadState;
	PyThreadState *mainThreadState;
	PyObject *pFunctionModule;
	PyObject *pPackageModule;
	PyObject *pICTModule;
} py_state_t;

typedef struct {
	char path_str[MAX_NODEPATH_SIZE];
	char *post_prefix_start;
	int post_prefix_len;
} path_t;

static py_state_t *auto_ddu_lib_init();
static void auto_ddu_lib_fini(py_state_t *py_state_p);
static PyObject *ai_call_ddu_devscan(py_state_t *py_state_p,
    boolean_t get_only_missing_drivers, char *dev_type);
static PyObject *ai_call_ddu_package_lookup(py_state_t *py_state_p,
    PyObject *pDevObj, PyObject *pRepoList);
static int ai_call_ddu_install_package(py_state_t *py_state_p,
    PyObject *ddu_package_obj, char *install_root, boolean_t third_party_ok);
static PyObject *ai_new_ddu_package_object(py_state_t *py_state_p,
    char *type, char *name, char *origin);
static int ai_get_ddu_package_object_values(PyObject *pDDUPackageObject,
    char **type, char **origin, char **name, char **descr, char **inf_link,
    boolean_t *third_party);
static int ai_get_ddu_dev_data_values(PyObject *pDDUDevData,
    char **dev_type, char **descr);
static void ai_du_process_manual_pkg(py_state_t *py_state_p,
    PyObject *pPackageList, char *origin, char *type, char *name,
    char *noinstall);
static void ai_du_process_manual_pkg_names(py_state_t *py_state_p,
    PyObject *pPackageList, path_t *path_p, char *origin, char *type,
    char *name);
static void ai_du_process_manual_pkg_types(py_state_t *py_state_p,
    PyObject *pPackageList, path_t *path_p, char *origin, char *type);
static PyObject *ai_du_get_manual_pkg_list(py_state_t *py_state_p,
    path_t *path_p);
static PyObject *ai_du_get_searched_pkg_list(py_state_t *py_state_p,
    path_t *path_p);
static int ai_du_install_packages(py_state_t *py_state_p,
    PyObject *pPkgTupleList, char *install_root, boolean_t honor_noinstall,
    int *num_installed_pkgs_p);
static char **ai_uniq_manifest_values(char **in, int *len_p);
static int ai_du_call_update_archive_ict(py_state_t *py_state_p,
    char *install_root);

/*
 * Stores the list of packages set up by ai_du_get_and_install() for use by
 * ai_du_install().
 */
static PyObject *py_pkg_list;

static char *empty_string = "";

/* Private functions. */

/*
 * auto_ddu_lib_init:
 * Initialize they python interpreter state so that python functions can be
 * called from this module.  Initialize a few common things always used.
 *
 * Arguments: None
 *
 * Returns:
 *   Success: A pointer to an initialized py_state_t object
 *   Failure: NULL
 *
 * Note: Call auto_ddu_lib_fini(), passing it the item returned from this
 * function, to undo the effects of this function.
 */
static py_state_t *
auto_ddu_lib_init()
{
	PyObject *pName;
	py_state_t *py_state_p = malloc(sizeof (py_state_t));

	py_state_p->pFunctionModule = NULL;
	py_state_p->pPackageModule = NULL;

	/* Set up python interpreter state. */
	PyEval_InitThreads();
	py_state_p->mainThreadState = PyThreadState_Get();
	py_state_p->myThreadState =
	    PyThreadState_New(py_state_p->mainThreadState->interp);
	PyThreadState_Swap(py_state_p->myThreadState);

	/* Get names of modules for use by python/C interfaces. */
	if ((pName = PyString_FromString(DDU_FUNCTION_MODULE)) != NULL) {
		py_state_p->pFunctionModule = PyImport_Import(pName);
		Py_DECREF(pName);
	}
	if ((pName = PyString_FromString(DDU_PACKAGE_MODULE)) != NULL) {
		py_state_p->pPackageModule = PyImport_Import(pName);
		Py_DECREF(pName);
	}
	if ((pName = PyString_FromString(ICT_MODULE)) != NULL) {
		py_state_p->pICTModule = PyImport_Import(pName);
		Py_DECREF(pName);
	}

	/* Cleanup and return NULL on error. */
	if ((py_state_p->pFunctionModule == NULL) ||
	    (py_state_p->pPackageModule == NULL) ||
	    (py_state_p->pICTModule == NULL)) {
		auto_debug_print(AUTO_DBGLVL_ERR, "auto_ddu_lib_init: "
		    "error accessing DDU library or ICT modules.\n");
		PyErr_Print();
		auto_ddu_lib_fini(py_state_p);
		py_state_p = NULL;
	}

	return (py_state_p);
}

/*
 * auto_ddu_lib_fini:
 * Undo initialization of python interpreter state set up by
 * auto_ddu_lib_init().
 *
 * Arguments: A pointer to an initialized py_state_t object
 *
 * Returns: N/A
 */
static void
auto_ddu_lib_fini(py_state_t *py_state_p)
{
	if (py_state_p == NULL) {
		return;
	}
	Py_XDECREF(py_state_p->pFunctionModule);
	Py_XDECREF(py_state_p->pPackageModule);
	Py_XDECREF(py_state_p->pICTModule);
	PyThreadState_Swap(py_state_p->mainThreadState);
	PyThreadState_Clear(py_state_p->myThreadState);
	PyThreadState_Delete(py_state_p->myThreadState);
	free(py_state_p);
}

/*
 * ai_call_ddu_build_repo_list:
 * Call the DDU library ddu_build_repo_list function.  This sets up the
 * list of repositories specified (as name/URL tuples) in the second argument,
 * and returns a python list of ddu_repo_objects for use by ddu_package_lookup.
 *
 * Arguments:
 *   py_state: Initialized py_state_t object.
 *   pRepoTypleList: List of (pubname, URL) tuples, each tuple representing
 *	a repository.
 *
 * Returns:
 *   Success: A python object representing a list of ddu_repo_objects.
 *   Failure: NULL
 */
static PyObject *
ai_call_ddu_build_repo_list(py_state_t *py_state_p, PyObject *pRepoTupleList)
{
	PyObject *pRet = NULL;

	/* Find the function */
	PyObject *pFunc = PyObject_GetAttrString(py_state_p->pFunctionModule,
	    DDU_BUILD_REPO_LIST);

	if ((pFunc == NULL) || (!PyCallable_Check(pFunc))) {
		auto_debug_print(AUTO_DBGLVL_ERR,
		    "Function not callable: %s\n", DDU_BUILD_REPO_LIST);
	} else {
		PyObject *pArgs = PyTuple_New(1);

		/*
		 * INCREF here since PyTuple_SetItem steals the reference, and
		 * can decrement the refcount when pArgs is DECREFed.
		 */
		Py_INCREF(pRepoTupleList);

		/* Set up args to python function and call it. */
		PyTuple_SetItem(pArgs, 0, pRepoTupleList);
		pRet = PyObject_CallObject(pFunc, pArgs);
		Py_DECREF(pArgs);

		if ((PyErr_Occurred()) || (pRet == NULL) || (pRet == Py_None)) {
			auto_debug_dump_file(AUTO_DBGLVL_ERR, DDU_ERRLOG);
			auto_debug_print(AUTO_DBGLVL_ERR,
			    "%s returned an error.\n", DDU_BUILD_REPO_LIST);
			if (PyErr_Occurred()) {
				PyErr_Print();
			}
			Py_XDECREF(pRet);
			pRet = NULL;
		}
	}

	Py_XDECREF(pFunc);
	return (pRet);
}

/*
 * ai_call_ddu_devscan:
 * Call the DDU library ddu_devscan function.  This function performs a device
 * scan on the system, to find out which devices are missing drivers.
 *
 * Arguments:
 *   py_state_p: Initialized py_state_t object.
 *   get_only_missing_drivers: Boolean: when true, return only the list of
 *	devices which are missing drivers.  When false, return all devices.
 *   dev_type: Type of devices to scan for.  See the DDU library ddu_devscan()
 *	function for the list of device types.  "all" is an acceptable device
 *	type.
 *
 * Returns:
 *   Success:
 *	A python object representing a list of ddu_dev_data objects is returned.
 *	- NOTE: if no devices are missing drivers and get_only_missing_drivers
 *	  is true, then an empty list is returned.
 *	- A ddu_dev_data object represents a found device.
 *   Failure:
 *	NULL
 */
static PyObject *
ai_call_ddu_devscan(py_state_t *py_state_p,
    boolean_t get_only_missing_drivers, char *dev_type)
{
	PyObject *pRet = NULL;

	/* Find the function */
	PyObject *pFunc = PyObject_GetAttrString(py_state_p->pFunctionModule,
	    DDU_DEVSCAN);

	if ((pFunc == NULL) || (!PyCallable_Check(pFunc))) {
		auto_debug_print(AUTO_DBGLVL_ERR,
		    "Function not callable: %s\n", DDU_DEVSCAN);
	} else {
		/* Set up args to python function and call it. */
		PyObject *pArgs = PyTuple_New(2);
		PyTuple_SetItem(pArgs, 0,
		    PyBool_FromLong((long)get_only_missing_drivers));
		PyTuple_SetItem(pArgs, 1, PyString_FromString(dev_type));
		pRet = PyObject_CallObject(pFunc, pArgs);

		Py_DECREF(pArgs);
		if ((PyErr_Occurred()) || (pRet == NULL) || (pRet == Py_None)) {
			auto_debug_dump_file(AUTO_DBGLVL_ERR, DDU_ERRLOG);
			auto_debug_print(AUTO_DBGLVL_ERR,
			    "%s returned an error.\n", DDU_DEVSCAN);
			if (PyErr_Occurred()) {
				PyErr_Print();
			}
			Py_XDECREF(pRet);
			pRet = NULL;
		}
	}

	Py_XDECREF(pFunc);
	return (pRet);
}

/*
 * ai_call_ddu_package_lookup:
 * Call the DDU library ddu_package_lookup function.  Given a list of
 * repositories, this function attempts to find a pkg(5) package in one of the
 * repositories.
 *
 * Arguments:
 *   py_state_p: Initialized py_state_t object.
 *   pDevObj: A python ddu_dev_data object representing a device.
 *   pRepoList: A python list of ddu_repo_object objects.  This represents the
 *	list of repositories to search through for a driver package.
 *
 * Returns:
 *   Success: A python ddu_package_object representing a package to install for
 *	the given device.
 *   Failure: NULL
 */
static PyObject *
ai_call_ddu_package_lookup(py_state_t *py_state_p,
    PyObject *pDevObj, PyObject *pRepoList)
{
	PyObject *pRet = NULL;

	/* Find the function */
	PyObject *pFunc = PyObject_GetAttrString(py_state_p->pFunctionModule,
	    DDU_PACKAGE_LOOKUP);

	if ((pFunc == NULL) || (!PyCallable_Check(pFunc))) {
		auto_debug_print(AUTO_DBGLVL_ERR,
		    "Function not callable: %s\n", DDU_PACKAGE_LOOKUP);
	} else {
		/* Set up args to python function. */
		PyObject *pArgs = PyTuple_New(2);
		Py_INCREF(pDevObj);	/* PyTuple_SetItem steals reference. */
		Py_INCREF(pRepoList);	/* PyTuple_SetItem steals reference. */
		PyTuple_SetItem(pArgs, 0, pDevObj);
		PyTuple_SetItem(pArgs, 1, pRepoList);

		/* Call ddu_package_lookup() */
		pRet = PyObject_CallObject(pFunc, pArgs);
		Py_DECREF(pArgs);
		if ((PyErr_Occurred()) || (pRet == NULL) || (pRet == Py_None)) {
			auto_debug_dump_file(AUTO_DBGLVL_ERR, DDU_ERRLOG);
			auto_debug_print(AUTO_DBGLVL_ERR,
			    "%s returned an error.\n", DDU_PACKAGE_LOOKUP);
			if (PyErr_Occurred()) {
				PyErr_Print();
			}
			Py_XDECREF(pRet);
			pRet = NULL;
		}
	}

	Py_XDECREF(pFunc);
	return (pRet);
}

/*
 * ai_call_ddu_install_package:
 * Call the DDU library ddu_install_package function.  Install the package
 * represented by pDDUPackageObj under the tree or file system given by
 * install_root.
 *
 * Arguments:
 *   py_state_p: Initialized py_state_t object.
 *   pDDUPackageObj: A python ddu_package_object representing the package to
 *	install.
 *   install_root: The root of the directory tree or file system in which to
 *	install the package.
 *   third_party_ok: Boolean: When true, it is OK to download and install
 *	packages found at third-party websites (as opposed to pkg(5)
 *	repositories).
 *
 * Returns:
 *   AUTO_INSTALL_SUCCESS: Package was successfully installed.
 *   AUTO_INSTALL_FAILURE: Package was not successfully installed.
 *
 * NOTE: check installer logfile for details of the failure.
 */
static int
ai_call_ddu_install_package(py_state_t *py_state_p,
    PyObject *pDDUPackageObj, char *install_root, boolean_t third_party_ok)
{
	int rval = AUTO_INSTALL_SUCCESS;

	/* Find the function */
	PyObject *pFunc = PyObject_GetAttrString(py_state_p->pFunctionModule,
	    DDU_INSTALL_PACKAGE);

	if ((pFunc == NULL) || (!PyCallable_Check(pFunc))) {
		auto_debug_print(AUTO_DBGLVL_ERR,
		    "Function not callable: %s\n", DDU_INSTALL_PACKAGE);
		rval = AUTO_INSTALL_FAILURE;

	} else {

		/* Set up args to python function. */
		PyObject *pArgs = PyTuple_New(3);
		Py_INCREF(pDDUPackageObj);	/* PyTuple_SetItem steals ref */
		PyTuple_SetItem(pArgs, 0, pDDUPackageObj);
		PyTuple_SetItem(pArgs, 1, PyString_FromString(install_root));
		PyTuple_SetItem(pArgs, 2,
		    PyBool_FromLong((long)third_party_ok));

		/* Call ddu_install_packages() */
		PyObject_CallObject(pFunc, pArgs);
		Py_DECREF(pArgs);

		if (PyErr_Occurred()) {
			auto_debug_dump_file(AUTO_DBGLVL_ERR, DDU_ERRLOG);
			auto_debug_print(AUTO_DBGLVL_ERR,
			    "%s returned an error\n", DDU_INSTALL_PACKAGE);
			PyErr_Print();
			rval = AUTO_INSTALL_FAILURE;
		}
	}

	Py_XDECREF(pFunc);
	return (rval);
}

/*
 * ai_new_ddu_package_object:
 * Create a new ddu_package_object of given type, name and origin.
 *
 * Arguments:
 *   py_state_p: Initialized py_state_t object.
 *   type: type of package.
 *   name: name of package. (Not used by all types of packages.)
 *   origin: directory of where package is located.
 *
 * Returns:
 *   Success: A new python ddu_package_object object of the given
 *	type/name/origin.
 *   Failure: NULL
 */
static PyObject *
ai_new_ddu_package_object(py_state_t *py_state_p,
    char *type, char *name, char *origin)
/*
 * Construct and return a new python ddu_package_object based on arguments.
 * Assumes auto_ddu_lib_init() has been called.
 *
 * Success: Returns the new object.
 * Failure: NULL
 */
{
	PyObject *pRet = NULL;

	/* Find the function */
	PyObject *pFunc = PyObject_GetAttrString(py_state_p->pPackageModule,
	    DDU_PACKAGE_OBJECT);

	if ((pFunc == NULL) || (!PyCallable_Check(pFunc))) {
		auto_debug_print(AUTO_DBGLVL_ERR,
		    "ddu_package_object constructor not callable\n");
	} else {
		/* Set up args to python function. */
		PyObject *pArgs = PyTuple_New(3);
		PyTuple_SetItem(pArgs, 0, PyString_FromString(type));
		PyTuple_SetItem(pArgs, 1, PyString_FromString(name));
		PyTuple_SetItem(pArgs, 2, PyString_FromString(origin));

		/* Call ddu_package_object constructor. */
		pRet = PyObject_CallObject(pFunc, pArgs);
		Py_DECREF(pArgs);
		if ((PyErr_Occurred()) || (pRet == NULL) || (pRet == Py_None)) {
			auto_debug_dump_file(AUTO_DBGLVL_ERR, DDU_ERRLOG);
			auto_debug_print(AUTO_DBGLVL_ERR,
			    "ddu_package_object constructor failed\n");
			if (PyErr_Occurred()) {
				PyErr_Print();
			}
			Py_XDECREF(pRet);
			pRet = NULL;
		}
	}

	Py_XDECREF(pFunc);
	return (pRet);
}

/*
 * ai_get_ddu_package_object_values:
 * Return selected values from a given ddu_package_object.
 *
 * Values returned live on the python interpreter's heap, and should not be
 * modified or freed.  They live only as long as the python obj they come from.
 *
 * Note: implementation is tied to the fields of the python object.
 *
 * Arguments:
 *   pDDUPackageObject: Object to extract values from.  Assumed to be a
 *	ddu_package_object;  not verified.
 *   type: char string pointer returned filled in with "pkg_type" field.
 *	Not processed if NULL.
 *   origin: char string pointer returned filled in with "pkg_location" field.
 *	Not processed if NULL.
 *   name: char string pointer returned filled in with "pkg_name" field.
 *	Not processed if NULL.
 *   descr: char string pointer returned filled in with "device_descriptor"
 *	field.  Not processed if NULL.
 *   inf_link: char string pointer returned filled in with "inf_link" field.
 *	Not processed if NULL.
 *   third_party: boolean pointer returned filled in with
 *	"third_party_from_search" field.  Not processed if NULL.
 *
 * Returns:
 *   AUTO_INSTALL_SUCCESS: when all requested fields are found and extracted.
 *   AUTO_INSTALL_FAILURE: when one or more fields could not be found or
 *	extracted.
 */
static int
ai_get_ddu_package_object_values(PyObject *pDDUPackageObject,
    char **type, char **origin, char **name, char **descr, char **inf_link,
    boolean_t *third_party)
{
	PyObject *pValue;

	if (type != NULL) {
		pValue = PyObject_GetAttrString(pDDUPackageObject, "pkg_type");
		if (pValue == NULL) {
			auto_debug_print(AUTO_DBGLVL_ERR,
			    "ai_get_ddu_package_object_values: "
			    "no ddu_package_object pkg_type field.\n");
			return (AUTO_INSTALL_FAILURE);
		}
		*type = PyString_AsString(pValue);
	}

	if (origin != NULL) {
		pValue = PyObject_GetAttrString(pDDUPackageObject,
		    "pkg_location");
		if (pValue == NULL) {
			auto_debug_print(AUTO_DBGLVL_ERR,
			    "ai_get_ddu_package_object_values: "
			    "no ddu_package_object pkg_location field.\n");
			return (AUTO_INSTALL_FAILURE);
		}
		*origin = PyString_AsString(pValue);
	}

	if (name != NULL) {
		pValue = PyObject_GetAttrString(pDDUPackageObject, "pkg_name");
		if (pValue == NULL) {
			auto_debug_print(AUTO_DBGLVL_ERR,
			    "ai_get_ddu_package_object_values: "
			    "no ddu_package_object pkg_name field.\n");
			return (AUTO_INSTALL_FAILURE);
		}
		*name = PyString_AsString(pValue);
	}

	if (descr != NULL) {
		pValue = PyObject_GetAttrString(pDDUPackageObject,
		    "device_descriptor");
		if (pValue == NULL) {
			auto_debug_print(AUTO_DBGLVL_ERR,
			    "ai_get_ddu_package_object_values: "
			    "no ddu_package_object device_descriptor field.\n");
			return (AUTO_INSTALL_FAILURE);
		}
		*descr = PyString_AsString(pValue);
	}

	if (inf_link != NULL) {
		pValue = PyObject_GetAttrString(pDDUPackageObject, "inf_link");
		if (pValue == NULL) {
			auto_debug_print(AUTO_DBGLVL_ERR,
			    "ai_get_ddu_package_object_values: "
			    "no ddu_package_object inf_link field.\n");
			return (AUTO_INSTALL_FAILURE);
		}
		*inf_link = PyString_AsString(pValue);
	}

	if (third_party != NULL) {
		pValue = PyObject_GetAttrString(pDDUPackageObject,
		    "third_party_from_search");
		if (pValue == NULL) {
			auto_debug_print(AUTO_DBGLVL_ERR,
			    "ai_get_ddu_package_object_values: "
			    "no ddu_package_object "
			    "third_party_from_search field.\n");
			return (AUTO_INSTALL_FAILURE);
		}
		*third_party = (PyObject_IsTrue(pValue));
	}

	return (AUTO_INSTALL_SUCCESS);
}

/*
 * ai_get_ddu_dev_data_values:
 * Return selected values from a given ddu_dev_data object.
 *
 * Values returned live on the python interpreter's heap, and should not be
 * modified or freed.  They live only as long as the python obj they come from.
 *
 * Note: implementation is tied to the fields of the python object.
 *
 * Arguments:
 *   pDDUDevData: Object to extract values from.  Assumed to be a
 *	ddu_dev_data object;  not verified.
 *   dev_type: char string pointer returned filled in with "device_type" field.
 *	Not processed if NULL.
 *   descr: char string pointer returned filled in with "description" field.
 *	Not processed if NULL.
 *
 * Returns:
 *   AUTO_INSTALL_SUCCESS: when all requested fields are found and extracted.
 *   AUTO_INSTALL_FAILURE: when one or more fields could not be found or
 *	extracted.
 */
static int
ai_get_ddu_dev_data_values(PyObject *pDDUDevData, char **dev_type, char **descr)
{
	PyObject *pValue;

	if (dev_type != NULL) {
		pValue = PyObject_GetAttrString(pDDUDevData, "device_type");
		if (pValue == NULL) {
			auto_debug_print(AUTO_DBGLVL_ERR,
			    "ai_get_ddu_dev_data_values: "
			    "no ddu_dev_data device_type field.\n");
			return (AUTO_INSTALL_FAILURE);
		}
		*dev_type = PyString_AsString(pValue);
	}

	if (descr != NULL) {
		pValue = PyObject_GetAttrString(pDDUDevData, "description");
		if (pValue == NULL) {
			auto_debug_print(AUTO_DBGLVL_ERR,
			    "ai_get_ddu_dev_data_values: "
			    "no ddu_dev_data description field.\n");
			return (AUTO_INSTALL_FAILURE);
		}
		*descr = PyString_AsString(pValue);
	}

	return (AUTO_INSTALL_SUCCESS);
}

/*
 * ai_du_process_manual_pkg:
 * Create a ddu_package_object from parameters, and add it to the pPackageList.
 *
 * Arguments:
 *   py_state_p: Initialized py_state_t object.
 *   pPackageList: List of packages to append the new ddu_package_object to.
 *   origin: directory of where package is located.
 *   type: type of package.
 *   name: name of package.
 *   noinstall: boolean whether package is to be installed only to booted
 *	environment, not to target.
 *
 * Returns: None
 *   Note 1: the pPackageList will be modified.
 *   Note 2: Appropriate error messages are logged/displayed.
 */
static void
ai_du_process_manual_pkg(py_state_t *py_state_p, PyObject *pPackageList,
    char *origin, char *type, char *name, char *noinstall)
{
	PyObject *pDDUPackageObject;
	PyObject *pTuple;

	auto_log_print(gettext("Found manifest entry for package:\n"));
	if (name != empty_string) {
		auto_log_print(gettext("  type:%s, origin:%s, name:%s\n"),
		    type, origin, name);
	} else {
		auto_log_print(gettext("  type:%s, origin:%s\n"),
		    type, origin);
	}
	if (strcmp(noinstall, "true") == 0) {
		auto_log_print(gettext("    Package to be "
		    "installed only in current booted environment.\n"));
	} else {
		auto_log_print(gettext("    Package to be "
		    "installed in current booted environment and target.\n"));
	}

	/* Initialize a new ddu_package_object object */
	pDDUPackageObject = ai_new_ddu_package_object(py_state_p,
	    type, name, origin);

	if (pDDUPackageObject == NULL) {
		auto_debug_print(AUTO_DBGLVL_ERR,
		    "ai_du_get_pkg_list: <add_drivers> error:\n"
		    "Error creating new package object for "
		    "origin %s %s\n", origin, name);
		return;
	}

	pTuple = PyTuple_New(3);
	PyTuple_SetItem(pTuple, 0, pDDUPackageObject);
	PyTuple_SetItem(pTuple, 1, Py_True);	/* third party OK */
	PyTuple_SetItem(pTuple, 2,
	    (strcmp(noinstall, "true") == 0) ? Py_True : Py_False);

	/*
	 * NOTE: Don't decref pTuple here as PyList_Append doesn't
	 * steal a reference to it.
	 */
	PyList_Append(pPackageList, pTuple);
}

/*
 * ai_du_process_manual_pkg_names:
 * Do any processing of packages for which unique origin, type and name are
 *	known.
 *
 * Arguments:
 *   py_state_p: Initialized py_state_t object.
 *   pPackageList: List of packages to append the new ddu_package_object to.
 *   origin: directory of where package is located.
 *   type: type of package.
 *   name: name of package.
 *
 * Returns: None
 *   Note 1: the pPackageList will be modified.
 *   Note 2: Appropriate error messages are logged/displayed.
 */
static void
ai_du_process_manual_pkg_names(py_state_t *py_state_p, PyObject *pPackageList,
    path_t *path_p, char *origin, char *type, char *name)
{
	char **actions;
	int actions_len;
	char *nodespec;

	/* Get the action attribute. */

	/* Search is different depending on whether a name is specified. */
	if (name == empty_string) {
		nodespec = ACTION_NONAME_NODEPATH;
	} else {
		nodespec = ACTION_YESNAME_NODEPATH;
	}

	if (snprintf(path_p->post_prefix_start, path_p->post_prefix_len,
	    nodespec, origin, type, name) >= path_p->post_prefix_len) {
		auto_debug_print(AUTO_DBGLVL_ERR, "ai_du_get_pkg_list: "
		    "<add_drivers> manifest error:\n"
		    "action path buffer overflow for origin \"%s\", "
		    "type \"%s\", name \"%s\"\n", origin, type, name);
		return;
	}

	actions = ai_get_manifest_values(path_p->path_str, &actions_len);

	/*
	 * Note: action must be present and must be either "install"
	 * or "noinstall".
	 */
	if (actions_len <= 0) {
		auto_log_print(gettext("ai_du_get_pkg_list: "
		    "<add_drivers> manifest error:\n"
		    "no action value for origin \"%s\", "
		    "type \"%s\", name \"%s\"\n"), origin, type, name);
		return;

	} else if (actions_len > 1) {
		auto_log_print(gettext("ai_du_get_pkg_list: "
		    "<add_drivers> manifest error:\n"
		    "multiple action values for origin \"%s\", "
		    "type \"%s\", name \"%s\"\n"), origin, type, name);
		return;

	} else if (strcmp(actions[0], "install") == 0) {
		/*
		 * If action="install" then call ai_du_process_manual_pkg with
		 * noinstall param set to empty_string, which means pkg will be
		 * installed in both boot env and target.
		 */

		/* Obj pointed to by pPackageList will be modified. */
		ai_du_process_manual_pkg(py_state_p, pPackageList, origin,
		    type, name, empty_string);
	} else if (strcmp(actions[0], "noinstall") == 0) {
		/*
		 * If action="noinstall" then call ai_du_process_manual_pkg with
		 * noinstall param set to "true", which means pkg will only be
		 * installed in both boot env and not in target.
		 */
		/* Obj pointed to by pPackageList will be modified. */
		ai_du_process_manual_pkg(py_state_p, pPackageList, origin,
		    type, name, "true");
	} else {
		auto_log_print(gettext("ai_du_get_pkg_list: "
		    "<add_drivers> manifest error:\n"
		    "action must be install or noinstall for origin \"%s\", "
		    "type \"%s\", name \"%s\"\n"), origin, type, name);
		return;

	}
	ai_free_manifest_values(actions);
}

/*
 * ai_du_process_manual_pkg_types:
 * Do any processing of packages for which unique origin and type are known.
 *
 * Arguments:
 *   py_state_p: Initialized py_state_t object.
 *   pPackageList: List of packages to append the new ddu_package_object to.
 *   origin: directory of where package is located.
 *   type: type of package.
 *
 * Returns: None
 *   Note 1: the pPackageList will be modified.
 *   Note 2: Appropriate error messages are logged/displayed.
 */
static void
ai_du_process_manual_pkg_types(py_state_t *py_state_p, PyObject *pPackageList,
    path_t *path_p, char *origin, char *type)
{
	char **names;
	char **uniq_names;
	int namelen;
	int k;

	if ((strcmp(type, "P5I") != 0) &&
	    (strcmp(type, "SVR4") != 0) &&
	    (strcmp(type, "DU") != 0)) {
		auto_log_print(gettext("ai_du_get_pkg_list: "
		    "<add_drivers> manifest error:\n"
		    "invalid type %s given for origin %s\n"),
		    type, origin);
		return;
	}

	/* Get all names assocated with type and origin. */

	if (snprintf(path_p->post_prefix_start, path_p->post_prefix_len,
	    NAME_NODEPATH, origin, type) >= path_p->post_prefix_len) {
		auto_debug_print(AUTO_DBGLVL_ERR, "ai_du_get_pkg_list: "
		    "<add_drivers> manifest error:\n"
		    "name path buffer overflow for origin "
		    "%s, type %s\n", origin, type);
		return;
	}

	names = ai_get_manifest_values(path_p->path_str, &namelen);
	uniq_names = ai_uniq_manifest_values(names, &namelen);
	ai_free_manifest_values(names);
	names = uniq_names;

	/* P5I and DU type entries don't have a "name" entry. */
	if (strcmp(type, "SVR4") != 0) {
		if (namelen > 0) {
			auto_log_print(gettext(
			    "ai_du_get_pkg_list: <add_drivers> "
			    "manifest error:\n"
			    "name given to P5I or DU pkg spec at "
			    "origin %s\n"), origin);
			return;
		} else {
			/* Obj pointed to by pPackageList will be modified. */
			ai_du_process_manual_pkg_names(py_state_p, pPackageList,
			    path_p, origin, type, empty_string);
		}

	/*
	 * There must be at least one "name" entry per pkg spec
	 * for SVR4 type.
	 */
	} else if (namelen <= 0) {
		auto_log_print(gettext("ai_du_get_pkg_list: "
		    "<add_drivers> manifest error:\n"
		    "no name given for SVR4 pkg spec at origin %s, type %s\n"),
		    origin, type);
		return;

	} else {
		/* Process each origin/type/name entry. */
		for (k = 0; k < namelen; k++) {

			/* Obj pointed to by pPackageList will be modified. */
			ai_du_process_manual_pkg_names(py_state_p, pPackageList,
			    path_p, origin, type, names[k]);
		}

	}
	ai_free_manifest_values(names);
}

/*
 * ai_du_get_manual_pkg_list:
 * Read the AI ai.xml Manifest file and process the <software> under the
 * <add_drivers> section.  A <software> is a manual specification of a package
 * to install.  Do error checking of the manifest as necessary, as this function
 * reads the manifest before it is validated against a schema.
 *
 * Validates syntax and processes the following from the manifest:
 *	<add_drivers>
 *		<software>
 *			<source>
 *				<publisher>
 *					<origin name="origin"/>
 *				</publisher>
 *			</source>
 *			<software_data type="type" action="noinstall">
 *				<name>"name"</name>
 *			</software_data>
 *		</software>
 *	</add_drivers>
 *
 *	type can be "SVR4", "P5I" or "DU".
 *	name not allowed if type is "P5I" or "DU"
 *
 * Arguments:
 *   py_state_p: Initialized py_state_t object.
 *   path_p: Used to build nodepath strings for manifest checking.
 *
 * Returns:
 *   Success: A python list of (ddu_package_object, third_party_ok, noinstall)
 *	tuples suitable for input to ai_du_install_packages().
 *	NOTE: if the manifest shows no packages to install for Driver Update,
 *	this function will return an empty list.
 *   Failure: NULL
 *
 * NOTE: check installer logfile for details of the failure.
 */
static PyObject *
ai_du_get_manual_pkg_list(py_state_t *py_state_p, path_t *path_p)
{
	PyObject *pPackageList = NULL;
	char **uniq_origins = NULL;
	char **types = NULL;
	int origin_len, typelen;
	char **origins;
	char **uniq_types;
	int num_pkg_specs;
	int i, j;

	/* Read manifest for specific package requests. */

	/* Get the number of pkg spec entries. */
	if (strlcpy(path_p->post_prefix_start, PKGSPEC_NODEPATH,
	    path_p->post_prefix_len) > path_p->post_prefix_len) {
		auto_debug_print(AUTO_DBGLVL_ERR,
		    "ai_du_get_pkg_list: pkg spec path buffer overflow\n");
		return (NULL);
	}

	/* Use "origins" like a dummy here.  Interest only in num_pkg_specs. */
	origins = ai_get_manifest_values(path_p->path_str, &num_pkg_specs);
	ai_free_manifest_values(origins);

	/* No pkg specs.  Return an empty list. */
	if (num_pkg_specs <= 0) {
		return (PyList_New(0));
	}

	/* Retrieve a list of all specific package request origins. */
	if (strlcpy(path_p->post_prefix_start, ORIGIN_NODEPATH,
	    path_p->post_prefix_len) > path_p->post_prefix_len) {
		auto_debug_print(AUTO_DBGLVL_ERR,
		    "ai_du_get_pkg_list: origin path buffer overflow\n");
		return (NULL);
	}

	/* Get real origins list here for use below. */
	origins = ai_get_manifest_values(path_p->path_str, &origin_len);

	/*
	 * Not a perfect test to validate pkg specs and origins in manifest,
	 * but it will do...
	 */
	if (origin_len != num_pkg_specs) {
		auto_debug_print(AUTO_DBGLVL_ERR,
		    "ai_du_get_pkg_list: <add_drivers> manifest error:\n"
		    "There is not a 1-1 origin to pkg spec mapping.\n");
		return (NULL);
	}

	uniq_origins = ai_uniq_manifest_values(origins, &origin_len);
	ai_free_manifest_values(origins);
	origins = uniq_origins;

	/*
	 * Initialize a zero-length list.
	 * This will be returned empty if nothing to install has been found.
	 */
	pPackageList = PyList_New(0);

	/*
	 * For each origin, get types.  Note it is possible for there to be
	 * more than one type at a origin.  There can also be more than one
	 * item of a given type at a origin.
	 */
	for (i = 0; i < origin_len; i++) {

		/* Process "type" entries. */

		if (snprintf(path_p->post_prefix_start, path_p->post_prefix_len,
		    TYPE_NODEPATH, origins[i]) >= path_p->post_prefix_len) {
			auto_debug_print(AUTO_DBGLVL_ERR,
			    "ai_du_get_pkg_list: "
			    "<add_drivers> manifest error:\n"
			    "type path buffer overflow for origin %s\n",
			    origins[i]);
			continue;
		}

		types = ai_get_manifest_values(path_p->path_str, &typelen);
		if (typelen <= 0) {
			auto_log_print(gettext("ai_du_get_pkg_list: "
			    "<add_drivers> manifest error:\n"
			    "no type given for origin %s\n"), origins[i]);
			continue;
		}

		uniq_types = ai_uniq_manifest_values(types, &typelen);
		ai_free_manifest_values(types);
		types = uniq_types;

		/* Loop for all types found at this origin... */
		for (j = 0; j < typelen; j++) {

			/* Obj pointed to by pPackageList will be modified. */
			ai_du_process_manual_pkg_types(py_state_p, pPackageList,
			    path_p, origins[i], types[j]);
		}
	}
	ai_free_manifest_values(origins);
	ai_free_manifest_values(types);
	return (pPackageList);
}

/*
 * ai_du_get_searched_pkg_list:
 * Read the AI ai.xml Manifest file and process the <search_all> tag under the
 * <add_drivers> section.  Do the needful to scan for missing devices and to
 * perform package searches and installs for missing drivers.  Do error
 * checking of the manifest as necessary, as this function reads the manifest
 * before it is validated against a schema.
 *
 * Validates syntax and processes the following from the manifest:
 *	<add_drivers>
 *		<search_all addall="false">
 *			<source>
 *				<publisher name="publisher">
 *					<origin name="origin"/>
 *				</publisher>
 *			</source>
 *		</search_all>
 *	</add_drivers>
 *
 *  publisher and origin are both optional, but if one is specified then the
 *  other must also be specified.
 *	addall is optional.  Defaults to "false" if not specified.
 *
 * Arguments:
 *   py_state_p: Initialized py_state_t object.
 *   path_p: Used to build nodepath strings for manifest checking.
 *
 * Returns:
 *   Success: A python list of (ddu_package_object, third_party_ok, noinstall)
 *	tuples suitable for input to ai_du_install_packages().
 *	NOTE: if the system is missing no drivers, this function will return
 *	an empty list.
 *   Failure: NULL
 *
 * NOTE: check installer logfile for details of the failure.
 */
static PyObject *
ai_du_get_searched_pkg_list(py_state_t *py_state_p, path_t *path_p)
{
	PyObject *pPackageList = NULL;
	PyObject *pDeviceList = NULL;
	PyObject *pTuple;
	PyObject *pRepoTupleList;
	int len, sublen;
	PyObject *pSearchRepoList = NULL;
	char *search_origin, *search_pub;
	PyObject *py_search_addall = NULL;
	char **searches = NULL;
	char **search_origins = NULL;
	char **search_pubs = NULL;
	char **search_addalls = NULL;
	Py_ssize_t i, listlen;
	PyObject *pRval = NULL;

	pPackageList = PyList_New(0); /* Initialize a zero-length list. */

	/* Read manifest for search requests. */

	if (strlcpy(path_p->post_prefix_start, SEARCH_NODEPATH,
	    path_p->post_prefix_len) > path_p->post_prefix_len) {
		auto_debug_print(AUTO_DBGLVL_ERR,
		    "ai_du_get_searched_pkg_list: "
		    "search pathname buffer overflow.\n");
		return (NULL);
	}

	searches = ai_get_manifest_values(path_p->path_str, &len);
	ai_free_manifest_values(searches);
	if (len > 1) {
		auto_log_print(gettext("ai_du_get_searched_pkg_list: "
		    "too many <search_all> entries in manifest\n"));
		return (NULL);

	} else if (len <= 0) {
		return (pPackageList);
	}

	auto_log_print(gettext("ai_du_get_searched_pkg_list: Doing a device "
	    "scan for devices which are missing drivers...\n"));

	/*
	 * Call ddu_devscan() to do search if requested.
	 * The boolean value is to scan only for missing drivers.
	 */
	pDeviceList = ai_call_ddu_devscan(py_state_p, B_TRUE, "all");
	if (pDeviceList == NULL) {
		auto_debug_print(AUTO_DBGLVL_ERR,
		    "ai_du_get_searched_pkg_list: "
		    "Error scanning for missing devices.\n");
		return (NULL);

	/* An empty list is perfectly acceptable here.  No missing drivers. */
	} else if (PyList_Size(pDeviceList) == 0) {
		return (pPackageList);
	}

	/* Get repo origin, if specified. */

	if (strlcpy(path_p->post_prefix_start, SEARCH_ORIGIN_NODEPATH,
	    path_p->post_prefix_len) > path_p->post_prefix_len) {
		auto_debug_print(AUTO_DBGLVL_ERR,
		    "ai_du_get_searched_pkg_list: search repo origin path "
		    "buffer overflow.\n");
		return (NULL);
	}

	auto_log_print(gettext("ai_du_get_searched_pkg_list: Querying manifest "
	    "for explicit repo for getting missing driver packages...\n"));

	search_origins = ai_get_manifest_values(path_p->path_str, &sublen);
	if (sublen == 1) {
		search_origin = search_origins[0];
	} else if (sublen <= 0) {
		search_origin = empty_string;
	} else {
		auto_log_print(gettext("ai_du_get_searched_pkg_list: "
		    "<add_drivers> manifest error:\n"
		    "Only one origin allowed per <search_all> entry.\n"));
		goto done;
	}

	/* Get repo publisher, if specified. */

	if (strlcpy(path_p->post_prefix_start, SEARCH_PUBNAME_NODEPATH,
	    path_p->post_prefix_len) > path_p->post_prefix_len) {
		auto_debug_print(AUTO_DBGLVL_ERR,
		    "ai_du_get_searched_pkg_list: search repo publisher path "
		    "buffer overflow.\n");
		goto done;
	}

	search_pubs = ai_get_manifest_values(path_p->path_str, &sublen);
	if (sublen == 1) {
		search_pub = search_pubs[0];
	} else if (sublen <= 0) {
		search_pub = empty_string;
	} else {
		auto_log_print(gettext("ai_du_get_searched_pkg_list: "
		    "<add_drivers> manifest error:\n"
		    "Only one publisher allowed for a <search_all> entry\n"));
		goto done;
	}

	/* Can't have one without the other. */
	if ((search_pub == empty_string) ^
	    (search_origin == empty_string)) {
		auto_log_print(gettext("ai_du_get_searched_pkg_list: "
		    "<add_drivers> manifest error:\n"
		    "search repo origin and "
		    "publisher must be specified together.\n"));
		goto done;
	}

	/*
	 * if publisher and origin provided, create tuple from them and
	 * build a repo list from it.
	 */
	if (search_pub != empty_string) {

		auto_log_print(gettext("ai_du_get_searched_pkg_list: "
		    "Found repo in manifest: publisher:%s, origin:%s\n"),
		    search_pub, search_origin);

		pTuple = PyTuple_New(2);
		PyTuple_SetItem(pTuple, 0, PyString_FromString(search_pub));
		PyTuple_SetItem(pTuple, 1, PyString_FromString(search_origin));
		pRepoTupleList = PyList_New(0);
		PyList_Append(pRepoTupleList, pTuple);
		pSearchRepoList = ai_call_ddu_build_repo_list(py_state_p,
		    pRepoTupleList);
		Py_DECREF(pTuple);
		Py_DECREF(pRepoTupleList);

		if (pSearchRepoList == NULL) {
			auto_debug_print(AUTO_DBGLVL_ERR,
			    "ai_du_get_searched_pkg_list:"
			    "Error building search repo list.\n");
			goto done;
		}
	} else {
		/* No publisher/URL provided.  Return an empty repo list. */

		auto_debug_print(AUTO_DBGLVL_INFO,
		    "ai_du_get_searched_pkg_list: "
		    "No <search_all> repo found in manifest\n");

		pSearchRepoList = PyList_New(0);
	}

	/* Find out if <addall> was specified. */

	if (strlcpy(path_p->post_prefix_start, SEARCH_ADDALL_NODEPATH,
	    path_p->post_prefix_len) > path_p->post_prefix_len) {
		auto_debug_print(AUTO_DBGLVL_ERR,
		    "ai_du_get_searched_pkg_list: search addall path "
		    "buffer overflow.\n");
		goto done;
	}

	/* No more than a single true/false value is allowed. */
	search_addalls = ai_get_manifest_values(path_p->path_str, &sublen);
	if ((sublen > 1) ||
	    ((sublen == 1) &&
	    ((strcmp(search_addalls[0], "true") != 0) &&
	    (strcmp(search_addalls[0], "false") != 0)))) {
		auto_log_print(gettext("ai_du_get_searched_pkg_list: "
		    "<add_drivers> manifest error:\n"
		    "invalid addall value for <search_all> entry\n"));
		goto done;

	/* Default to false if not provided. */
	} else if ((sublen <= 0) || (strcmp(search_addalls[0], "false") == 0)) {
		Py_INCREF(Py_False);
		py_search_addall = Py_False;

	} else {
		auto_log_print(gettext("ai_du_get_searched_pkg_list: Manifest "
		    "allows adding of third-party drivers\n"));
		Py_INCREF(Py_True);
		py_search_addall = Py_True;
	}

	/*
	 * Append packages found for missing devices, to the list of packages
	 * to install.
	 */
	listlen = PyList_Size(pDeviceList);
	for (i = 0; i < listlen; i++) {

		PyObject *pDDUPackageObject;
		PyObject *pDDUDevData;
		char *dev_type;
		char *descr;
		boolean_t third_party = B_FALSE;

		pDDUDevData = PyList_GetItem(pDeviceList, i);

		/* Find the package containing the driver for this device. */
		pDDUPackageObject = ai_call_ddu_package_lookup(py_state_p,
		    pDDUDevData, pSearchRepoList);

		/* Get info for display / logging purposes, and log it. */
		if (ai_get_ddu_dev_data_values(pDDUDevData,
		    &dev_type, &descr) != AUTO_INSTALL_SUCCESS) {
			auto_debug_print(AUTO_DBGLVL_ERR,
			    "ai_du_get_searched_pkg_list: Error retrieving "
			    "device information for display\n");
			dev_type = descr = empty_string;
		}

		if (pDDUPackageObject == NULL) {
			auto_log_print(gettext("ai_du_get_searched_pkg_list: "
			    "Error retrieving package for "
			    "\"%s\" type device \"%s\".\n"), dev_type, descr);
			continue;
		} else {
			auto_log_print(gettext("ai_du_get_searched_pkg_list: "
			    "DDU returned package info for "
			    "\"%s\" type device \"%s\".\n"), dev_type, descr);
		}

		(void) ai_get_ddu_package_object_values(pDDUPackageObject,
		    NULL, NULL, NULL, NULL, NULL, &third_party);
		if (third_party) {
			auto_log_print(gettext("  This is a third-party "
			    "package.\n"));
		}

		/* Append the package info to the returned list. */

		/*
		 * NOTE: Don't decref pTuple here as PyList_Append doesn't
		 * steal a reference to it.
		 */
		pTuple = PyTuple_New(3);
		PyTuple_SetItem(pTuple, 0, pDDUPackageObject);
		Py_INCREF(py_search_addall);
		PyTuple_SetItem(pTuple, 1, py_search_addall); /* 3rd party OK */
		Py_INCREF(Py_False);
		PyTuple_SetItem(pTuple, 2, Py_False);	/* always install */

		PyList_Append(pPackageList, pTuple);
	}

	/* Success.  Prepare to return the package list just prepared. */
	pRval = pPackageList;

done:
	/* Cleanup time, whether an error occured or not. */
	ai_free_manifest_values(search_origins);
	ai_free_manifest_values(search_pubs);
	Py_XDECREF(py_search_addall);
	Py_XDECREF(pSearchRepoList);
	Py_XDECREF(pDeviceList);
	return (pRval);
}

/*
 * ai_du_install_packages:
 * Install packages provided by the pPkgTupleList.  Install in the filesystem /
 * tree under install_root, skipping packages with noinstall flag set if
 * honor_noinstall is set.
 *
 * Arguments:
 *   py_state_p: Initialized py_state_t object.
 *   pPkgTupleList: Python list of (ddu_package_object, third_party_ok,
 *	noinstall) tuples which define packages to add and their parameters for
 *	adding them.  Guaranteed not to be NULL.  When third_party_ok is true,
 *	add the corresponding package even if it is to be downloaded from a
 *	third party website.  When noinstall is true in the tuple and the
 *	honor_noinstall argument is also true, skip adding the corresponding
 *	package.  (This may be used to skip installation onto the target disk,
 *	after having installed into the booted install environment.)
 *   install_root: Top of the filesystem or tree where the packages are to be
 *	installed.
 *   honor_noinstall: When true and the noinstall flag is set in a package
 *	tuple, skip installing that package.
 *   num_installed_pkgs_p: Returns the value passed in, plus the number of
 *	packages actually installed.
 *
 * Returns:
 *   AUTO_INSTALL_SUCCESS: All packages were able to be installed.
 *   AUTO_INSTALL_FAILURE: At least one package was not able to be installed.
 *
 * NOTE: check installer logfile for details of the failure.
 */
static int
ai_du_install_packages(py_state_t *py_state_p, PyObject *pPkgTupleList,
    char *install_root, boolean_t honor_noinstall, int *num_installed_pkgs_p)
{
	Py_ssize_t len;
	Py_ssize_t i;
	int rval = AUTO_INSTALL_SUCCESS;

	auto_log_print(gettext("ai_du_install_packages: "
	    "Installing packages to %s\n"), install_root);

	len = PyList_Size(pPkgTupleList);
	for (i = 0; i < len; i++) {

		/* Break out the tuple. */

		PyObject *pTuple = PyList_GetItem(pPkgTupleList, i);
		PyObject *pDDUPackageObject = PyTuple_GetItem(pTuple, 0);
		PyObject *pThirdPartyOK = PyTuple_GetItem(pTuple, 1);
		PyObject *pNoInstall = PyTuple_GetItem(pTuple, 2);
		char *type = empty_string;
		char *origin = empty_string;
		char *name = empty_string;
		char *descr = empty_string;
		char *inf_link = empty_string;
		boolean_t third_party;

		if (ai_get_ddu_package_object_values(pDDUPackageObject,
		    &type, &origin, &name, &descr, &inf_link, &third_party) !=
		    AUTO_INSTALL_SUCCESS) {
			auto_debug_print(AUTO_DBGLVL_ERR,
			    "ai_du_install_packages: Error extracting package "
			    "information for ddu_package_object.\n");
			type = origin = name = descr = inf_link =
			    empty_string;
			third_party = B_FALSE;
		} else {
			if (strcmp(name, empty_string) == 0) {
				auto_log_print(gettext(
				    "  %s package at origin:%s\n"),
				    type, origin);
			} else {
				auto_log_print(gettext(
				    "  %s package at origin:%s, name:%s\n"),
				    type, origin, name);
			}
		}

		if (PyObject_IsTrue(pNoInstall) && honor_noinstall) {
			auto_log_print(gettext("ai_du_install_packages: "
			    "    honoring noinstall: skipping package.\n"));
			continue;
		}

		/* Display any third-party package errors to console. */
		if ((! PyObject_IsTrue(pThirdPartyOK)) && third_party) {
			char *msg1_3p = gettext("  Manifest is not allowing "
			    "third party packages found through search for "
			    "installation to %s\n");
			char *msg2_3p = gettext("  Info on the package to "
			    "install to make device \"%s\"\n"
			    "    operational is available:\n"
			    "    %s\n");
			(void) fprintf(stderr, msg1_3p, install_root);
			auto_log_print(msg1_3p, install_root);
			(void) fprintf(stderr, msg2_3p, descr, inf_link);
			auto_log_print(msg2_3p, descr, inf_link);
			rval = AUTO_INSTALL_FAILURE;
			continue;
		}

		/* Handle uninstallable package objects. */
		if (strcmp(origin, empty_string) == 0) {
			if (strcmp(inf_link, empty_string) == 0) {
				auto_log_print(gettext(
				    "ai_du_install_packages: Package not "
				    "found for device: \"%s\"\n"), descr);
			} else {
				auto_log_print(gettext(
				    "ai_du_install_packages: Package for "
				    "device: \"%s\" must be installed "
				    "manually.\n"
				    "For more information go to:\n %s\n"),
				    descr, inf_link);
			}
			rval = AUTO_INSTALL_FAILURE;
			continue;
		}

		/* All is well.  Install the package. */
		if (ai_call_ddu_install_package(py_state_p, pDDUPackageObject,
		    install_root, PyObject_IsTrue(pThirdPartyOK)) ==
		    AUTO_INSTALL_FAILURE) {
			auto_log_print(gettext("ai_du_install_packages: "
			    "Error installing package to %s\n"), install_root);
			rval = AUTO_INSTALL_FAILURE;
		} else {
			(*num_installed_pkgs_p)++;
		}
	}
	return (rval);
}

/*
 * ai_uniq_manifest_values:
 * Remove duplicate values in lists returned by ai_lookup_manifest_values().
 *
 * Arguments:
 *   in: the input list of values.
 *   len_p: The length of the input list, on input.  Returns the length of the
 *	list returned.
 *
 * Returns:
 *   Success: The resulting list of unique values.
 *   Failure: NULL
 */
static char **
ai_uniq_manifest_values(char **in, int *len_p)
{
	int in_len = *len_p;
	int dup_count = 0;
	char **out;
	char *comp;
	boolean_t *is_dup = (boolean_t *)alloca(in_len * sizeof (boolean_t));
	int i, j;

	if ((in_len == 0) || (in == NULL)) {
		return (NULL);
	}

	bzero(is_dup, (in_len * sizeof (boolean_t)));

	for (i = 0; i < in_len - 1; i++) {
		comp = in[i];
		for (j = i + 1; j < in_len; j++) {
			if ((!is_dup[j]) && (strcmp(comp, in[j]) == 0)) {
				is_dup[j] = B_TRUE;
				dup_count++;
			}
		}
	}

	out = (char **)malloc((in_len - dup_count + 1) * sizeof (char *));
	for (i = 0, j = 0; i < in_len; i++) {
		if (!is_dup[i]) {
			out[j++] = strdup(in[i]);
		}
	}
	out[j] = NULL;

	*len_p = j;
	return (out);
}

/*
 * ai_du_call_update_archive_ict:
 * Call the bootadm update_archive ICT
 *
 * Arguments:
 *   py_state_p: Initialized py_state_t object.
 *   install_root: root of the tree where to call the ICT.
 *
 * Returns:
 *   AUTO_INSTALL_SUCCESS: ICT was successfully executed.
 *   AUTO_INSTALL_FAILURE: ICT was not successfully executed.
 */
static int
ai_du_call_update_archive_ict(py_state_t *py_state_p, char *install_root)
{
	int rval = AUTO_INSTALL_FAILURE;
	PyObject *pICT_instance = NULL;
	PyObject *pICT_rval = NULL;

	/* Find the constructor. */
	PyObject *pFunc = PyObject_GetAttrString(py_state_p->pICTModule,
	    ICT_CLASS);

	if ((pFunc == NULL) || (!PyCallable_Check(pFunc))) {
		auto_debug_print(AUTO_DBGLVL_ERR,
		    "ICT constructor not callable\n");
	} else {
		/* Set up args to python function. */
		PyObject *pArgs = PyTuple_New(1);
		PyTuple_SetItem(pArgs, 0, PyString_FromString(install_root));

		/* Call constructor. */
		pICT_instance = PyObject_CallObject(pFunc, pArgs);
		Py_XDECREF(pFunc);
		Py_DECREF(pArgs);
		if ((PyErr_Occurred()) || (pICT_instance == NULL) ||
		    (pICT_instance == Py_None)) {
			auto_debug_dump_file(AUTO_DBGLVL_ERR, DDU_ERRLOG);
			auto_debug_print(AUTO_DBGLVL_ERR,
			    "ICT constructor failed\n");
			if (PyErr_Occurred()) {
				PyErr_Print();
			}
			Py_CLEAR(pICT_instance);
		}
	}

	if (pICT_instance != NULL) {
		pICT_rval = PyObject_CallMethod(
		    pICT_instance, ICT_UPDATE_ARCHIVE, NULL);
		if (pICT_rval == NULL) {
			auto_debug_print(AUTO_DBGLVL_ERR,
			    "Error running update_boot_archive ICT.\n");
		} else if (PyInt_AsLong(pICT_rval) != 0) {
			auto_debug_print(AUTO_DBGLVL_ERR,
			    "update_boot_archive ICT returned an error.\n");
		} else {
			rval = AUTO_INSTALL_SUCCESS;
		}
		Py_XDECREF(pICT_rval);
		Py_DECREF(pICT_instance);
	}

	return (rval);
}

/* Exported functions. */

/*
 * ai_du_get_and_install:
 * Query the manifest for the entire <add_drivers> section, and add packages
 * accordingly.  Add packages to install_root.  If a package has its noinstall
 * flag set and the honor_noinstall argument is set, skip adding that package.
 * Save the list of packages to install to the module global py_pkg_list, so
 * that the same list of packages can be installed to a different target with
 * ai_du_install().
 *
 * Install all explicitly-stated packages first.  Then do <search_all> last.
 * This is to handle any explicit requests for matching a special driver to a
 * device, before <search_all> finds the first available one.
 *
 * Assumes ai_create_manifest_image() has set up the manifest data.
 * Does not assume any data has been verified though.
 *
 * Arguments:
 *   install_root: Top of the filesystem or tree where the packages are to be
 *	installed.
 *   honor_noinstall: When true and the noinstall flag is set in a package
 *	tuple, skip installing that package.
 *   update_boot_archive: When true, run the ICT to update the boot archive.
 *
 * Returns:
 *   The number of packages successfully processed if all are successfully
 *	processed.  NOTE: this can be zero if there are none to process.
 *   -1: One or more packages could not be successfully processed.
 *   Boot archive update status is not reflected in this return status.
 *   NOTE: this routine will continue on most errors, in order to install as
 *	many packages as possible.
 *
 * NOTE: check installer logfile for details of the failure.
 *
 * Side effects:
 *   module global py_pkg_list is set to point to list of packages to install.
 */
int
ai_du_get_and_install(char *install_root, boolean_t honor_noinstall,
    boolean_t update_boot_archive)
{
	PyObject *manual_pkg_list;
	PyObject *searched_pkg_list;
	py_state_t *py_state_p;
	path_t path;
	char **dummy_list;
	int num_entries;
	int len;
	Py_ssize_t manual_size = 0;
	Py_ssize_t searched_size = 0;
	int rval = 0;
	int num_pkgs_installed = 0;

	/* Initialize path, post_prefix_start and post_prefix_len for later. */
	(void) strncpy(path.path_str, AIM_PREFACE, MAX_NODEPATH_SIZE);
	len = strlen(path.path_str);
	path.post_prefix_start = &path.path_str[len];
	path.post_prefix_len = MAX_NODEPATH_SIZE - len;

	/*
	 * Set up an empty py_pkg_list so ai_du_install() knows this function
	 * was called first.
	 */
	if (py_pkg_list != NULL) {
		Py_CLEAR(py_pkg_list);
	}

	py_pkg_list = PyList_New(0);

	/*
	 * See if the manifest has at least one pkg spec or searchall entry.
	 * If not, just return success (e.g. no-op).
	 */

	/* Get the number of pkg spec entries. */
	if (strlcpy(path.post_prefix_start, PKGSPEC_NODEPATH,
	    path.post_prefix_len) > path.post_prefix_len) {
		auto_debug_print(AUTO_DBGLVL_ERR,
		    "ai_du_get_and_install: pkg spec path buffer overflow\n");
		return (-1);
	}

	/* Get number of pkg spec entries in the manifest. */
	dummy_list = ai_get_manifest_values(path.path_str, &num_entries);
	ai_free_manifest_values(dummy_list);

	if (num_entries <= 0) {
		/* See if there is a searchall entry in the manifest. */
		if (strlcpy(path.post_prefix_start, SEARCH_NODEPATH,
		    path.post_prefix_len) > path.post_prefix_len) {
			auto_debug_print(AUTO_DBGLVL_ERR,
			    "ai_du_get_and_install: "
			    "search path buffer overflow\n");
			return (-1);
		}

		dummy_list = ai_get_manifest_values(path.path_str,
		    &num_entries);
		ai_free_manifest_values(dummy_list);
		if (num_entries <= 0) {
			return (0);
		}
	}

	/*
	 * Install all explicitly specified packages first.
	 *
	 * Do the search for missing devices afterward, as an independent step,
	 * to account for newly-operational devices as a result of
	 * explicitly-specified package installation.
	 */

	if ((py_state_p = auto_ddu_lib_init()) == NULL) {
		auto_debug_print(AUTO_DBGLVL_ERR,
		    "ai_du_get_and_install: "
		    "Error initializing auto_ddu_lib.\n");
		rval = -1;
		goto done;
	}

	manual_pkg_list = ai_du_get_manual_pkg_list(py_state_p, &path);
	if (manual_pkg_list == NULL) {
		auto_debug_print(AUTO_DBGLVL_ERR,
		    "ai_du_get_and_install: "
		    "Error getting package specification.\n");
		rval = -1;
		/* Keep going.  Don't abort. */
	} else {
		manual_size = PyList_Size(manual_pkg_list);
		if (manual_size > 0) {
			if (ai_du_install_packages(py_state_p, manual_pkg_list,
			    install_root, honor_noinstall,
			    &num_pkgs_installed) != AUTO_INSTALL_SUCCESS) {
				auto_debug_print(AUTO_DBGLVL_ERR,
				    "ai_du_get_and_install: Error installing "
				    "at least one package.\n");
				rval = -1;
				/* Keep going.  Don't abort. */
			}
		}
	}

	searched_pkg_list = ai_du_get_searched_pkg_list(py_state_p, &path);
	if (searched_pkg_list == NULL) {
		auto_debug_print(AUTO_DBGLVL_ERR,
		    "ai_du_get_and_install: "
		    "Error searching for inoperable devices and "
		    "missing driver packages.\n");
		rval = -1;
		/* Keep going.  Don't abort. */
	} else {
		searched_size = PyList_Size(searched_pkg_list);
		if (searched_size > 0) {
			if (ai_du_install_packages(py_state_p,
			    searched_pkg_list, install_root, honor_noinstall,
			    &num_pkgs_installed) != AUTO_INSTALL_SUCCESS) {
				auto_debug_print(AUTO_DBGLVL_ERR,
				    "ai_du_get_and_install: Error installing "
				    "at least one searched package "
				    "for <search_all>.\n");
				rval = -1;
				/* Keep going.  Don't abort. */
			}
		}
	}

	if ((update_boot_archive) && (num_pkgs_installed > 0)) {
		if (ai_du_call_update_archive_ict(py_state_p, install_root) !=
		    AUTO_INSTALL_SUCCESS) {
			auto_debug_print(AUTO_DBGLVL_ERR,
			    "ai_du_get_and_install: Warning: could not update "
			    "boot archive for %s.\n", install_root);
		}
	}

	/*
	 * Save the manual and searched package lists in py_pkg_list.
	 * The new list can be used in a later call to ai_du_install().
	 */

	if (manual_size > 0) {
		PyList_SetSlice(py_pkg_list, 0, manual_size - 1,
		    manual_pkg_list);
	}

	if (searched_size > 0) {
		PyList_SetSlice(py_pkg_list, manual_size,
		    manual_size + searched_size - 1, searched_pkg_list);
	}
done:
	auto_ddu_lib_fini(py_state_p);

	if (rval != -1) {
		rval = num_pkgs_installed;
	}
	return (rval);
}

/*
 * ai_du_install:
 * Install additional packages based on driver update parameters fetched from a
 * previous ai_du_get_and_install() call.  The module global py_pkg_list
 * supplies the list (and order) of packages to install.  Add packages to
 * install_root.  If a package has its noinstall flag set and the
 * honor_noinstall argument is set, skip adding that package.
 *
 * This routine assumes the py_pkg_list was set up via a prior call to
 * ai_du_get_and_install_packages().
 *
 * The availability and origin of all packages to be installed is assumed the
 * same as when the py_pkg_list was built (i.e. the most recent call to
 * ai_du_get_and_install()).
 *
 * Arguments:
 *   install_root: Top of the filesystem or tree where the packages are to be
 *	installed.
 *   honor_noinstall: When true and the noinstall flag is set in a package
 *	tuple, skip installing that package.
 *   update_boot_archive: When true, run the ICT to update the boot archive.
 *   NOTE: the modular global py_pkg_list specifies the packages to install.
 *
 * Returns:
 *   The number of packages successfully processed if all are successfully
 *	processed.  NOTE: this can be zero if there are none to process.
 *   -1: At least one package was not able to be installed.
 *   Boot archive update status is not reflected in this return status.
 *
 * NOTE: check installer logfile for details of the failure.
 */
int
ai_du_install(char *install_root, boolean_t honor_noinstall,
    boolean_t update_boot_archive)
{
	int rval = 0;
	int num_pkgs_installed = 0;

	py_state_t *py_state_p;

	if (py_pkg_list == NULL) {
		auto_debug_print(AUTO_DBGLVL_ERR,
		    "ai_du_install: ai_du_get_and_install needs to be "
		    "called first.\n");
		return (-1);

	} else if (PyList_Size(py_pkg_list) == 0) {
		return (num_pkgs_installed);
	}

	if ((py_state_p = auto_ddu_lib_init()) == NULL) {
		auto_debug_print(AUTO_DBGLVL_ERR,
		    "ai_du_install: "
		    "Error initializing auto_ddu_lib.\n");
		return (-1);
	}

	if ((rval = ai_du_install_packages(py_state_p, py_pkg_list,
	    install_root, honor_noinstall, &num_pkgs_installed)) !=
	    AUTO_INSTALL_SUCCESS) {
		auto_debug_print(AUTO_DBGLVL_ERR,
		    "ai_du_install: Error installing packages.\n");
		rval = -1;
	}

	if (update_boot_archive && (num_pkgs_installed > 0)) {
		if (ai_du_call_update_archive_ict(py_state_p,
		    install_root) != AUTO_INSTALL_SUCCESS) {
			auto_debug_print(AUTO_DBGLVL_ERR,
			    "ai_du_install: Warning: could not update boot "
			    "archive for %s.\n", install_root);
		}
	}

	if (rval != -1) {
		rval = num_pkgs_installed;
	}

	auto_ddu_lib_fini(py_state_p);

	return (rval);
}