OpenSolaris_b135/cmd/svc/prophist/prophist.c

Compare this file to the similar file:
Show the results in this format:

/*
 * 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 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*
 * prophist - property history utility
 *
 * 1.  Description
 *
 * During the development of smf(5), a set of service manifests were delivered
 * that required subsequent changes.  The bulk of these changes are in ON,
 * although additional consolidations may possess one or two manifests that are
 * affected.  These incorrect values need to be smoothed into a correct
 * configuration surface for subsequent automatic merge technology to be
 * introduced safely.  The mechanism is the combination of this utility with a
 * set of "property history" files.
 *
 * /var/svc/profile/prophist.SUNWcsr is delivered as an immutable file by the
 * SUNWcsr packages.  prophist.SUNWcsr covers the entire ON consolidation, for
 * the purposes of collecting in one place what is essentially a temporary
 * construct.  Other consolidations should deliver /var/svc/profile/prophist.*
 * files.
 *
 * The processing of the property history files occurs in
 * svc:/system/manifest-import:default.  Each prophist.* file is checked against
 * its hashed value in smf/manifest using the "hash" subcommand.  If a change is
 * detected, the prophist.* file is sourced.  These operations are carried out
 * prior to any manifest being imported.
 *
 * 2.  Interface
 *
 * prophist presents a subcommand style interface, with various suboptions to
 * each subcommand:
 *
 * prophist delete -e FMRI -g pg [-p prop]
 * prophist upgrade -e FMRI -g pg -p prop -n newval oldval ...
 * prophist overwrite -e FMRI -g pg -p prop -n newval
 * prophist hash file
 *
 * The hash subcommand signals that a file requires processing using an exit
 * status of 3.  Otherwise, exit statuses of 0, 1, and 2 have their conventional
 * meaning.
 *
 * 3.  Limitations
 *
 * The present implementation has no support for multiply-valued properties.
 * Manipulation of such properties should be done using a svccfg(1M) invocation
 * in the appropriate prophist.* file.
 */

#include <sys/types.h>

#include <assert.h>
#include <libintl.h>
#include <libscf.h>
#include <libscf_priv.h>
#include <libuutil.h>
#include <locale.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>

#include <manifest_hash.h>

#define	OPTIONS_STR	"e:g:n:p:"

static int o_delete;
static int o_hash;
static int o_overwrite;

static char *entity;
static char *pgrp_name;
static char *prop_name;
static char *new_value;

static scf_handle_t *hndl;
static scf_service_t *svc;
static scf_instance_t *inst;
static scf_snapshot_t *snap;
static scf_snaplevel_t *level;
static scf_propertygroup_t *pg;
static scf_property_t *prop;
static scf_value_t *value;
static scf_iter_t *iter;
static scf_transaction_t *tx;
static scf_transaction_entry_t *entry;

static scf_type_t ptype;

static char *valbuf;
static ssize_t valbuf_sz;

#define	LG_BUFSIZ	1024		/* larger than a property name */
static char namebuf[LG_BUFSIZ];

static void
usage()
{
	(void) fprintf(stderr, gettext(
	    "Usage:"
	    "\tprophist hash file\n"
	    "\tprophist delete -e FMRI -g pg [-p prop]\n"
	    "\tprophist overwrite -e FMRI -g pg -p prop -n newval\n"
	    "\tprophist upgrade -e FMRI -g pg -p prop -n newval oldval "
	    "...\n"));
	exit(UU_EXIT_USAGE);
}

static void
ready_scf_objects()
{
	if ((hndl = scf_handle_create(SCF_VERSION)) == NULL)
		uu_die(gettext("handle creation failed: %s\n"),
		    scf_strerror(scf_error()));

	if (scf_handle_bind(hndl) != 0)
		uu_die(gettext("handle bind failed: %s\n"),
		    scf_strerror(scf_error()));

	svc = scf_service_create(hndl);
	inst = scf_instance_create(hndl);
	snap = scf_snapshot_create(hndl);
	level = scf_snaplevel_create(hndl);
	pg = scf_pg_create(hndl);
	prop = scf_property_create(hndl);
	value = scf_value_create(hndl);
	iter = scf_iter_create(hndl);
	tx = scf_transaction_create(hndl);
	entry = scf_entry_create(hndl);

	if (svc == NULL ||
	    inst == NULL ||
	    snap == NULL ||
	    level == NULL ||
	    pg == NULL ||
	    prop == NULL ||
	    value == NULL ||
	    iter == NULL ||
	    tx == NULL ||
	    entry == NULL)
		uu_die(gettext("object creation failed: %s\n"),
		    scf_strerror(scf_error()));

	valbuf_sz = 4096;
	valbuf = malloc(valbuf_sz);
	if (valbuf == NULL)
		uu_die(gettext("value buffer allocation failed"));
}

static int
hash(char *arg)
{
	char *pname;
	char *errstr;
	int ret;
	uchar_t hash[MHASH_SIZE];

	ready_scf_objects();

	switch (ret = mhash_test_file(hndl, arg, 0, &pname, hash)) {
	case MHASH_RECONCILED:
		/* Equivalent hash already stored. */
		return (0);
	case MHASH_NEWFILE:
		/* Hash differs. */
		break;
	case MHASH_FAILURE:
		uu_die(gettext("mhash_test_file() failed"));
	default:
		uu_die(gettext("unknown return value (%d) from "
		    "mhash_test_file()"), ret);
	}

	if (mhash_store_entry(hndl, pname, arg, hash, &errstr)) {
		if (errstr)
			uu_die(errstr);
		else
			uu_die(gettext("Unknown error from "
			    "mhash_store_entry()\n"));
	}

	return (3);
}

static int
delete_prop(scf_propertygroup_t *pg, char *prop_name)
{
	if (scf_transaction_start(tx, pg) != 0)
		uu_die(gettext("transaction start failed: %s\n"),
		    scf_strerror(scf_error()));
	if (scf_transaction_property_delete(tx, entry, prop_name) != 0)
		uu_die(gettext("transaction property delete failed: %s\n"),
		    scf_strerror(scf_error()));
	if (scf_transaction_commit(tx) != 1)
		return (1);

	return (0);
}

/*
 * Returns 1 if target property group or property not found.
 */
static int
delete_pg_or_prop(scf_iter_t *pg_iter, char *pgrp_name, char *prop_name)
{
	while (scf_iter_next_pg(pg_iter, pg) > 0) {
		if (scf_pg_get_name(pg, namebuf, LG_BUFSIZ) == -1)
			continue;

		if (strcmp(namebuf, pgrp_name) != 0)
			continue;

		if (prop_name != NULL)
			return (delete_prop(pg, prop_name));

		if (scf_pg_delete(pg) != 0)
			uu_die(gettext("property group delete failed: %s\n"),
			    scf_strerror(scf_error()));

		return (0);
	}

	return (1);
}

/*
 * Remove property group or property from both service and instance.
 */
static int
delete(char *entity, char *pgrp_name, char *prop_name)
{
	ready_scf_objects();

	if (scf_handle_decode_fmri(hndl, entity, NULL, svc, inst, NULL, NULL,
	    SCF_DECODE_FMRI_EXACT) == 0) {
		(void) scf_iter_instance_pgs(iter, inst);
		return (delete_pg_or_prop(iter, pgrp_name, prop_name));
	}

	if (scf_handle_decode_fmri(hndl, entity, NULL, svc, NULL, NULL,
	    NULL, SCF_DECODE_FMRI_EXACT) == 0) {
		(void) scf_iter_service_pgs(iter, svc);
		return (delete_pg_or_prop(iter, pgrp_name, prop_name));
	}

	uu_die(gettext("%s not decoded: %s\n"), entity,
	    scf_strerror(scf_error()));

	/*NOTREACHED*/
}

static void
replace_value(scf_propertygroup_t *pg, char *prop_name, char *new_value)
{
	int result;
	int ret;

	do {
		if (scf_pg_update(pg) == -1)
			uu_die(gettext("property group update failed: %s\n"),
			    scf_strerror(scf_error()));
		if (scf_transaction_start(tx, pg) != SCF_SUCCESS) {
			if (scf_error() == SCF_ERROR_PERMISSION_DENIED)
				uu_die(gettext("permission denied\n"));

			uu_die(gettext("transaction start failed: %s\n"),
			    scf_strerror(scf_error()));
		}

		ret = scf_pg_get_property(pg, prop_name, prop);
		if (ret == SCF_SUCCESS) {
			if (scf_property_type(prop, &ptype) != SCF_SUCCESS)
				uu_die(gettext("couldn't get property type\n"));
			if (scf_transaction_property_change_type(tx, entry,
			    prop_name, ptype) == -1)
				uu_die(gettext("couldn't change entry\n"));
		} else if (scf_error() == SCF_ERROR_INVALID_ARGUMENT) {
			uu_die(gettext("illegal property name\n"));
		} else {
			uu_die(gettext("property fetch failed\n"));
		}

		if (scf_value_set_from_string(value, ptype,
		    (const char *)new_value) != 0) {
			assert(scf_error() == SCF_ERROR_INVALID_ARGUMENT);
			uu_die(gettext("Invalid \"%s\" value \"%s\".\n"),
			    scf_type_to_string(ptype), new_value);
		}

		ret = scf_entry_add_value(entry, value);
		if (ret != SCF_SUCCESS)
			uu_die(gettext("scf_entry_add_value failed: %s\n"),
			    scf_strerror(scf_error()));

		assert(ret == SCF_SUCCESS);

		result = scf_transaction_commit(tx);

		scf_transaction_reset(tx);
		scf_entry_destroy_children(entry);
	} while (result == 0);

	if (result < 0) {
		if (scf_error() != SCF_ERROR_PERMISSION_DENIED)
			uu_die(gettext("transaction commit failed: %s\n"),
			    scf_strerror(scf_error()));

		uu_die(gettext("permission denied\n"));
	}
}

static scf_propertygroup_t *
get_pg(char *entity, char *pgrp_name, char *prop_name)
{
	scf_propertygroup_t *targetpg;

	ready_scf_objects();

	if (scf_handle_decode_fmri(hndl, entity, NULL, svc, inst, NULL, NULL,
	    SCF_DECODE_FMRI_EXACT) == 0) {
		/*
		 * 1.  Working at the instance level.  The instance level
		 * contains one special case:  general/enabled is active in the
		 * current version, and its value in snapshots is not relevant.
		 * Otherwise, pull from running snapshot.
		 */
		if (strcmp(pgrp_name, "general") == 0 &&
		    strcmp(prop_name, "enabled") == 0) {
			if (scf_instance_get_pg(inst, pgrp_name, pg) == 0)
				return (pg);

			uu_die(gettext("property group %s not available: %s\n"),
			    pgrp_name, scf_strerror(scf_error()));
		}

		if (scf_instance_get_snapshot(inst, "running", snap) == -1) {
			if (scf_instance_get_pg(inst, pgrp_name, pg) == 0)
				return (pg);

			uu_die(gettext("property group %s not available: %s\n"),
			    pgrp_name, scf_strerror(scf_error()));
		}

		if (scf_snapshot_get_base_snaplevel(snap, level) != 0)
			uu_die(gettext("base snaplevel not available: %s\n"),
			    scf_strerror(scf_error()));

		if (scf_snaplevel_get_pg(level, pgrp_name, pg) == -1)
			uu_die(gettext("property group %s not available: %s\n"),
			    pgrp_name, scf_strerror(scf_error()));

		targetpg = scf_pg_create(hndl);
		if (scf_instance_get_pg(inst, pgrp_name, targetpg) == -1)
			uu_die(gettext("property group %s not available: %s\n"),
			    pgrp_name, scf_strerror(scf_error()));

		return (targetpg);
	}

	if (scf_handle_decode_fmri(hndl, entity, NULL, svc, NULL, NULL,
	    NULL, SCF_DECODE_FMRI_EXACT) == 0) {
		/*
		 * 2.  Working at the service level.
		 */
		if (scf_service_get_pg(svc, pgrp_name, pg) == 0)
			return (pg);

		uu_die(gettext("property group %s not available: %s\n"),
		    pgrp_name, scf_strerror(scf_error()));
	}

	/*
	 * 3.  Cannot decode either instance or service exactly.
	 */
	uu_die(gettext("%s not decoded: %s\n"), entity,
	    scf_strerror(scf_error()));

	/*NOTREACHED*/
}

static int
upgrade(char *entity, char *pgrp_name, char *prop_name, char *new_value,
    int argc, char *argv[], int optind)
{
	int replace = 0;
	int vals = 0;
	scf_propertygroup_t *targetpg;

	targetpg = get_pg(entity, pgrp_name, prop_name);

	if (scf_pg_get_property(targetpg, prop_name, prop) != 0)
		uu_die(gettext("property %s/%s not available: %s\n"), pgrp_name,
		    prop_name, scf_strerror(scf_error()));

	if (scf_iter_property_values(iter, prop) != 0)
		uu_die(gettext("could not establish value iterator: %s\n"),
		    scf_strerror(scf_error()));

	while (scf_iter_next_value(iter, value) == 1) {
		if (scf_value_get_as_string(value, valbuf, valbuf_sz) < 0)
			uu_die(gettext("string value get failed: %s\n"),
			    scf_strerror(scf_error()));

		for (; optind < argc; optind++)
			if (strcmp(valbuf, argv[optind]) == 0) {
				replace = 1;
				break;
			}

		vals++;
		if (vals > 1)
			uu_die(gettext("too many values to upgrade\n"));
	}

	if (replace)
		replace_value(targetpg, prop_name, new_value);

	return (0);
}

static int
overwrite(char *entity, char *pgrp_name, char *prop_name, char *new_value)
{
	scf_propertygroup_t *targetpg;

	targetpg = get_pg(entity, pgrp_name, prop_name);

	if (scf_pg_get_property(targetpg, prop_name, prop) != 0)
		uu_die(gettext("property %s/%s not available: %s\n"), pgrp_name,
		    prop_name, scf_strerror(scf_error()));

	replace_value(targetpg, prop_name, new_value);

	return (0);
}

int
main(int argc, char *argv[])
{
	int c;

	if (argc < 2)
		usage();

	if (strcmp(argv[1], "hash") == 0)
		o_hash = 1;
	else if (strcmp(argv[1], "delete") == 0)
		o_delete = 1;
	else if (strcmp(argv[1], "overwrite") == 0)
		o_overwrite = 1;
	else if (strcmp(argv[1], "upgrade") != 0)
		usage();

	(void) uu_setpname(argv[0]);

	argv++;
	argc--;

	while ((c = getopt(argc, argv, OPTIONS_STR)) != EOF) {
		switch (c) {
		case 'e':
			entity = optarg;
			break;
		case 'g':
			pgrp_name = optarg;
			break;
		case 'n':
			new_value = optarg;
			break;
		case 'p':
			prop_name = optarg;
			break;
		case '?':
		default:
			usage();
			break;
		}
	}

	if (o_hash) {
		if (entity != NULL ||
		    pgrp_name != NULL ||
		    prop_name != NULL ||
		    new_value != NULL)
			usage();

		return (hash(argv[optind]));
	}

	if (entity == NULL)
		usage();

	if (o_delete) {
		if (pgrp_name == NULL ||
		    new_value != NULL ||
		    optind < argc)
			usage();

		return (delete(entity, pgrp_name, prop_name));
	}

	if (pgrp_name == NULL || prop_name == NULL || new_value == NULL)
		usage();

	if (o_overwrite)
		return (overwrite(entity, pgrp_name, prop_name, new_value));

	if (optind >= argc)
		usage();

	return (upgrade(entity, pgrp_name, prop_name, new_value, argc, argv,
	    optind));
}