Home | History | Annotate | Download | only in prophist
      1 /*
      2  * CDDL HEADER START
      3  *
      4  * The contents of this file are subject to the terms of the
      5  * Common Development and Distribution License (the "License").
      6  * You may not use this file except in compliance with the License.
      7  *
      8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
      9  * or http://www.opensolaris.org/os/licensing.
     10  * See the License for the specific language governing permissions
     11  * and limitations under the License.
     12  *
     13  * When distributing Covered Code, include this CDDL HEADER in each
     14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
     15  * If applicable, add the following below this CDDL HEADER, with the
     16  * fields enclosed by brackets "[]" replaced with your own identifying
     17  * information: Portions Copyright [yyyy] [name of copyright owner]
     18  *
     19  * CDDL HEADER END
     20  */
     21 /*
     22  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
     23  * Use is subject to license terms.
     24  */
     25 
     26 /*
     27  * prophist - property history utility
     28  *
     29  * 1.  Description
     30  *
     31  * During the development of smf(5), a set of service manifests were delivered
     32  * that required subsequent changes.  The bulk of these changes are in ON,
     33  * although additional consolidations may possess one or two manifests that are
     34  * affected.  These incorrect values need to be smoothed into a correct
     35  * configuration surface for subsequent automatic merge technology to be
     36  * introduced safely.  The mechanism is the combination of this utility with a
     37  * set of "property history" files.
     38  *
     39  * /var/svc/profile/prophist.SUNWcsr is delivered as an immutable file by the
     40  * SUNWcsr packages.  prophist.SUNWcsr covers the entire ON consolidation, for
     41  * the purposes of collecting in one place what is essentially a temporary
     42  * construct.  Other consolidations should deliver /var/svc/profile/prophist.*
     43  * files.
     44  *
     45  * The processing of the property history files occurs in
     46  * svc:/system/manifest-import:default.  Each prophist.* file is checked against
     47  * its hashed value in smf/manifest using the "hash" subcommand.  If a change is
     48  * detected, the prophist.* file is sourced.  These operations are carried out
     49  * prior to any manifest being imported.
     50  *
     51  * 2.  Interface
     52  *
     53  * prophist presents a subcommand style interface, with various suboptions to
     54  * each subcommand:
     55  *
     56  * prophist delete -e FMRI -g pg [-p prop]
     57  * prophist upgrade -e FMRI -g pg -p prop -n newval oldval ...
     58  * prophist overwrite -e FMRI -g pg -p prop -n newval
     59  * prophist hash file
     60  *
     61  * The hash subcommand signals that a file requires processing using an exit
     62  * status of 3.  Otherwise, exit statuses of 0, 1, and 2 have their conventional
     63  * meaning.
     64  *
     65  * 3.  Limitations
     66  *
     67  * The present implementation has no support for multiply-valued properties.
     68  * Manipulation of such properties should be done using a svccfg(1M) invocation
     69  * in the appropriate prophist.* file.
     70  */
     71 
     72 #include <sys/types.h>
     73 
     74 #include <assert.h>
     75 #include <libintl.h>
     76 #include <libscf.h>
     77 #include <libscf_priv.h>
     78 #include <libuutil.h>
     79 #include <locale.h>
     80 #include <stdio.h>
     81 #include <stdlib.h>
     82 #include <string.h>
     83 #include <strings.h>
     84 #include <unistd.h>
     85 
     86 #include <manifest_hash.h>
     87 
     88 #define	OPTIONS_STR	"e:g:n:p:"
     89 
     90 static int o_delete;
     91 static int o_hash;
     92 static int o_overwrite;
     93 
     94 static char *entity;
     95 static char *pgrp_name;
     96 static char *prop_name;
     97 static char *new_value;
     98 
     99 static scf_handle_t *hndl;
    100 static scf_service_t *svc;
    101 static scf_instance_t *inst;
    102 static scf_snapshot_t *snap;
    103 static scf_snaplevel_t *level;
    104 static scf_propertygroup_t *pg;
    105 static scf_property_t *prop;
    106 static scf_value_t *value;
    107 static scf_iter_t *iter;
    108 static scf_transaction_t *tx;
    109 static scf_transaction_entry_t *entry;
    110 
    111 static scf_type_t ptype;
    112 
    113 static char *valbuf;
    114 static ssize_t valbuf_sz;
    115 
    116 #define	LG_BUFSIZ	1024		/* larger than a property name */
    117 static char namebuf[LG_BUFSIZ];
    118 
    119 static void
    120 usage()
    121 {
    122 	(void) fprintf(stderr, gettext(
    123 	    "Usage:"
    124 	    "\tprophist hash file\n"
    125 	    "\tprophist delete -e FMRI -g pg [-p prop]\n"
    126 	    "\tprophist overwrite -e FMRI -g pg -p prop -n newval\n"
    127 	    "\tprophist upgrade -e FMRI -g pg -p prop -n newval oldval "
    128 	    "...\n"));
    129 	exit(UU_EXIT_USAGE);
    130 }
    131 
    132 static void
    133 ready_scf_objects()
    134 {
    135 	if ((hndl = scf_handle_create(SCF_VERSION)) == NULL)
    136 		uu_die(gettext("handle creation failed: %s\n"),
    137 		    scf_strerror(scf_error()));
    138 
    139 	if (scf_handle_bind(hndl) != 0)
    140 		uu_die(gettext("handle bind failed: %s\n"),
    141 		    scf_strerror(scf_error()));
    142 
    143 	svc = scf_service_create(hndl);
    144 	inst = scf_instance_create(hndl);
    145 	snap = scf_snapshot_create(hndl);
    146 	level = scf_snaplevel_create(hndl);
    147 	pg = scf_pg_create(hndl);
    148 	prop = scf_property_create(hndl);
    149 	value = scf_value_create(hndl);
    150 	iter = scf_iter_create(hndl);
    151 	tx = scf_transaction_create(hndl);
    152 	entry = scf_entry_create(hndl);
    153 
    154 	if (svc == NULL ||
    155 	    inst == NULL ||
    156 	    snap == NULL ||
    157 	    level == NULL ||
    158 	    pg == NULL ||
    159 	    prop == NULL ||
    160 	    value == NULL ||
    161 	    iter == NULL ||
    162 	    tx == NULL ||
    163 	    entry == NULL)
    164 		uu_die(gettext("object creation failed: %s\n"),
    165 		    scf_strerror(scf_error()));
    166 
    167 	valbuf_sz = 4096;
    168 	valbuf = malloc(valbuf_sz);
    169 	if (valbuf == NULL)
    170 		uu_die(gettext("value buffer allocation failed"));
    171 }
    172 
    173 static int
    174 hash(char *arg)
    175 {
    176 	char *pname;
    177 	char *errstr;
    178 	int ret;
    179 	uchar_t hash[MHASH_SIZE];
    180 
    181 	ready_scf_objects();
    182 
    183 	switch (ret = mhash_test_file(hndl, arg, 0, &pname, hash)) {
    184 	case MHASH_RECONCILED:
    185 		/* Equivalent hash already stored. */
    186 		return (0);
    187 	case MHASH_NEWFILE:
    188 		/* Hash differs. */
    189 		break;
    190 	case MHASH_FAILURE:
    191 		uu_die(gettext("mhash_test_file() failed"));
    192 	default:
    193 		uu_die(gettext("unknown return value (%d) from "
    194 		    "mhash_test_file()"), ret);
    195 	}
    196 
    197 	if (mhash_store_entry(hndl, pname, arg, hash, &errstr)) {
    198 		if (errstr)
    199 			uu_die(errstr);
    200 		else
    201 			uu_die(gettext("Unknown error from "
    202 			    "mhash_store_entry()\n"));
    203 	}
    204 
    205 	return (3);
    206 }
    207 
    208 static int
    209 delete_prop(scf_propertygroup_t *pg, char *prop_name)
    210 {
    211 	if (scf_transaction_start(tx, pg) != 0)
    212 		uu_die(gettext("transaction start failed: %s\n"),
    213 		    scf_strerror(scf_error()));
    214 	if (scf_transaction_property_delete(tx, entry, prop_name) != 0)
    215 		uu_die(gettext("transaction property delete failed: %s\n"),
    216 		    scf_strerror(scf_error()));
    217 	if (scf_transaction_commit(tx) != 1)
    218 		return (1);
    219 
    220 	return (0);
    221 }
    222 
    223 /*
    224  * Returns 1 if target property group or property not found.
    225  */
    226 static int
    227 delete_pg_or_prop(scf_iter_t *pg_iter, char *pgrp_name, char *prop_name)
    228 {
    229 	while (scf_iter_next_pg(pg_iter, pg) > 0) {
    230 		if (scf_pg_get_name(pg, namebuf, LG_BUFSIZ) == -1)
    231 			continue;
    232 
    233 		if (strcmp(namebuf, pgrp_name) != 0)
    234 			continue;
    235 
    236 		if (prop_name != NULL)
    237 			return (delete_prop(pg, prop_name));
    238 
    239 		if (scf_pg_delete(pg) != 0)
    240 			uu_die(gettext("property group delete failed: %s\n"),
    241 			    scf_strerror(scf_error()));
    242 
    243 		return (0);
    244 	}
    245 
    246 	return (1);
    247 }
    248 
    249 /*
    250  * Remove property group or property from both service and instance.
    251  */
    252 static int
    253 delete(char *entity, char *pgrp_name, char *prop_name)
    254 {
    255 	ready_scf_objects();
    256 
    257 	if (scf_handle_decode_fmri(hndl, entity, NULL, svc, inst, NULL, NULL,
    258 	    SCF_DECODE_FMRI_EXACT) == 0) {
    259 		(void) scf_iter_instance_pgs(iter, inst);
    260 		return (delete_pg_or_prop(iter, pgrp_name, prop_name));
    261 	}
    262 
    263 	if (scf_handle_decode_fmri(hndl, entity, NULL, svc, NULL, NULL,
    264 	    NULL, SCF_DECODE_FMRI_EXACT) == 0) {
    265 		(void) scf_iter_service_pgs(iter, svc);
    266 		return (delete_pg_or_prop(iter, pgrp_name, prop_name));
    267 	}
    268 
    269 	uu_die(gettext("%s not decoded: %s\n"), entity,
    270 	    scf_strerror(scf_error()));
    271 
    272 	/*NOTREACHED*/
    273 }
    274 
    275 static void
    276 replace_value(scf_propertygroup_t *pg, char *prop_name, char *new_value)
    277 {
    278 	int result;
    279 	int ret;
    280 
    281 	do {
    282 		if (scf_pg_update(pg) == -1)
    283 			uu_die(gettext("property group update failed: %s\n"),
    284 			    scf_strerror(scf_error()));
    285 		if (scf_transaction_start(tx, pg) != SCF_SUCCESS) {
    286 			if (scf_error() == SCF_ERROR_PERMISSION_DENIED)
    287 				uu_die(gettext("permission denied\n"));
    288 
    289 			uu_die(gettext("transaction start failed: %s\n"),
    290 			    scf_strerror(scf_error()));
    291 		}
    292 
    293 		ret = scf_pg_get_property(pg, prop_name, prop);
    294 		if (ret == SCF_SUCCESS) {
    295 			if (scf_property_type(prop, &ptype) != SCF_SUCCESS)
    296 				uu_die(gettext("couldn't get property type\n"));
    297 			if (scf_transaction_property_change_type(tx, entry,
    298 			    prop_name, ptype) == -1)
    299 				uu_die(gettext("couldn't change entry\n"));
    300 		} else if (scf_error() == SCF_ERROR_INVALID_ARGUMENT) {
    301 			uu_die(gettext("illegal property name\n"));
    302 		} else {
    303 			uu_die(gettext("property fetch failed\n"));
    304 		}
    305 
    306 		if (scf_value_set_from_string(value, ptype,
    307 		    (const char *)new_value) != 0) {
    308 			assert(scf_error() == SCF_ERROR_INVALID_ARGUMENT);
    309 			uu_die(gettext("Invalid \"%s\" value \"%s\".\n"),
    310 			    scf_type_to_string(ptype), new_value);
    311 		}
    312 
    313 		ret = scf_entry_add_value(entry, value);
    314 		if (ret != SCF_SUCCESS)
    315 			uu_die(gettext("scf_entry_add_value failed: %s\n"),
    316 			    scf_strerror(scf_error()));
    317 
    318 		assert(ret == SCF_SUCCESS);
    319 
    320 		result = scf_transaction_commit(tx);
    321 
    322 		scf_transaction_reset(tx);
    323 		scf_entry_destroy_children(entry);
    324 	} while (result == 0);
    325 
    326 	if (result < 0) {
    327 		if (scf_error() != SCF_ERROR_PERMISSION_DENIED)
    328 			uu_die(gettext("transaction commit failed: %s\n"),
    329 			    scf_strerror(scf_error()));
    330 
    331 		uu_die(gettext("permission denied\n"));
    332 	}
    333 }
    334 
    335 static scf_propertygroup_t *
    336 get_pg(char *entity, char *pgrp_name, char *prop_name)
    337 {
    338 	scf_propertygroup_t *targetpg;
    339 
    340 	ready_scf_objects();
    341 
    342 	if (scf_handle_decode_fmri(hndl, entity, NULL, svc, inst, NULL, NULL,
    343 	    SCF_DECODE_FMRI_EXACT) == 0) {
    344 		/*
    345 		 * 1.  Working at the instance level.  The instance level
    346 		 * contains one special case:  general/enabled is active in the
    347 		 * current version, and its value in snapshots is not relevant.
    348 		 * Otherwise, pull from running snapshot.
    349 		 */
    350 		if (strcmp(pgrp_name, "general") == 0 &&
    351 		    strcmp(prop_name, "enabled") == 0) {
    352 			if (scf_instance_get_pg(inst, pgrp_name, pg) == 0)
    353 				return (pg);
    354 
    355 			uu_die(gettext("property group %s not available: %s\n"),
    356 			    pgrp_name, scf_strerror(scf_error()));
    357 		}
    358 
    359 		if (scf_instance_get_snapshot(inst, "running", snap) == -1) {
    360 			if (scf_instance_get_pg(inst, pgrp_name, pg) == 0)
    361 				return (pg);
    362 
    363 			uu_die(gettext("property group %s not available: %s\n"),
    364 			    pgrp_name, scf_strerror(scf_error()));
    365 		}
    366 
    367 		if (scf_snapshot_get_base_snaplevel(snap, level) != 0)
    368 			uu_die(gettext("base snaplevel not available: %s\n"),
    369 			    scf_strerror(scf_error()));
    370 
    371 		if (scf_snaplevel_get_pg(level, pgrp_name, pg) == -1)
    372 			uu_die(gettext("property group %s not available: %s\n"),
    373 			    pgrp_name, scf_strerror(scf_error()));
    374 
    375 		targetpg = scf_pg_create(hndl);
    376 		if (scf_instance_get_pg(inst, pgrp_name, targetpg) == -1)
    377 			uu_die(gettext("property group %s not available: %s\n"),
    378 			    pgrp_name, scf_strerror(scf_error()));
    379 
    380 		return (targetpg);
    381 	}
    382 
    383 	if (scf_handle_decode_fmri(hndl, entity, NULL, svc, NULL, NULL,
    384 	    NULL, SCF_DECODE_FMRI_EXACT) == 0) {
    385 		/*
    386 		 * 2.  Working at the service level.
    387 		 */
    388 		if (scf_service_get_pg(svc, pgrp_name, pg) == 0)
    389 			return (pg);
    390 
    391 		uu_die(gettext("property group %s not available: %s\n"),
    392 		    pgrp_name, scf_strerror(scf_error()));
    393 	}
    394 
    395 	/*
    396 	 * 3.  Cannot decode either instance or service exactly.
    397 	 */
    398 	uu_die(gettext("%s not decoded: %s\n"), entity,
    399 	    scf_strerror(scf_error()));
    400 
    401 	/*NOTREACHED*/
    402 }
    403 
    404 static int
    405 upgrade(char *entity, char *pgrp_name, char *prop_name, char *new_value,
    406     int argc, char *argv[], int optind)
    407 {
    408 	int replace = 0;
    409 	int vals = 0;
    410 	scf_propertygroup_t *targetpg;
    411 
    412 	targetpg = get_pg(entity, pgrp_name, prop_name);
    413 
    414 	if (scf_pg_get_property(targetpg, prop_name, prop) != 0)
    415 		uu_die(gettext("property %s/%s not available: %s\n"), pgrp_name,
    416 		    prop_name, scf_strerror(scf_error()));
    417 
    418 	if (scf_iter_property_values(iter, prop) != 0)
    419 		uu_die(gettext("could not establish value iterator: %s\n"),
    420 		    scf_strerror(scf_error()));
    421 
    422 	while (scf_iter_next_value(iter, value) == 1) {
    423 		if (scf_value_get_as_string(value, valbuf, valbuf_sz) < 0)
    424 			uu_die(gettext("string value get failed: %s\n"),
    425 			    scf_strerror(scf_error()));
    426 
    427 		for (; optind < argc; optind++)
    428 			if (strcmp(valbuf, argv[optind]) == 0) {
    429 				replace = 1;
    430 				break;
    431 			}
    432 
    433 		vals++;
    434 		if (vals > 1)
    435 			uu_die(gettext("too many values to upgrade\n"));
    436 	}
    437 
    438 	if (replace)
    439 		replace_value(targetpg, prop_name, new_value);
    440 
    441 	return (0);
    442 }
    443 
    444 static int
    445 overwrite(char *entity, char *pgrp_name, char *prop_name, char *new_value)
    446 {
    447 	scf_propertygroup_t *targetpg;
    448 
    449 	targetpg = get_pg(entity, pgrp_name, prop_name);
    450 
    451 	if (scf_pg_get_property(targetpg, prop_name, prop) != 0)
    452 		uu_die(gettext("property %s/%s not available: %s\n"), pgrp_name,
    453 		    prop_name, scf_strerror(scf_error()));
    454 
    455 	replace_value(targetpg, prop_name, new_value);
    456 
    457 	return (0);
    458 }
    459 
    460 int
    461 main(int argc, char *argv[])
    462 {
    463 	int c;
    464 
    465 	if (argc < 2)
    466 		usage();
    467 
    468 	if (strcmp(argv[1], "hash") == 0)
    469 		o_hash = 1;
    470 	else if (strcmp(argv[1], "delete") == 0)
    471 		o_delete = 1;
    472 	else if (strcmp(argv[1], "overwrite") == 0)
    473 		o_overwrite = 1;
    474 	else if (strcmp(argv[1], "upgrade") != 0)
    475 		usage();
    476 
    477 	(void) uu_setpname(argv[0]);
    478 
    479 	argv++;
    480 	argc--;
    481 
    482 	while ((c = getopt(argc, argv, OPTIONS_STR)) != EOF) {
    483 		switch (c) {
    484 		case 'e':
    485 			entity = optarg;
    486 			break;
    487 		case 'g':
    488 			pgrp_name = optarg;
    489 			break;
    490 		case 'n':
    491 			new_value = optarg;
    492 			break;
    493 		case 'p':
    494 			prop_name = optarg;
    495 			break;
    496 		case '?':
    497 		default:
    498 			usage();
    499 			break;
    500 		}
    501 	}
    502 
    503 	if (o_hash) {
    504 		if (entity != NULL ||
    505 		    pgrp_name != NULL ||
    506 		    prop_name != NULL ||
    507 		    new_value != NULL)
    508 			usage();
    509 
    510 		return (hash(argv[optind]));
    511 	}
    512 
    513 	if (entity == NULL)
    514 		usage();
    515 
    516 	if (o_delete) {
    517 		if (pgrp_name == NULL ||
    518 		    new_value != NULL ||
    519 		    optind < argc)
    520 			usage();
    521 
    522 		return (delete(entity, pgrp_name, prop_name));
    523 	}
    524 
    525 	if (pgrp_name == NULL || prop_name == NULL || new_value == NULL)
    526 		usage();
    527 
    528 	if (o_overwrite)
    529 		return (overwrite(entity, pgrp_name, prop_name, new_value));
    530 
    531 	if (optind >= argc)
    532 		usage();
    533 
    534 	return (upgrade(entity, pgrp_name, prop_name, new_value, argc, argv,
    535 	    optind));
    536 }
    537