prophist.c revision 449975fd500a154ec93bafe3fc6a5913c5bb82a5
/*
* 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 2006 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
/*
* 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, 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));
}