10Sstevel@tonic-gate /*
20Sstevel@tonic-gate * CDDL HEADER START
30Sstevel@tonic-gate *
40Sstevel@tonic-gate * The contents of this file are subject to the terms of the
51061Scasper * Common Development and Distribution License (the "License").
61061Scasper * You may not use this file except in compliance with the License.
70Sstevel@tonic-gate *
80Sstevel@tonic-gate * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
90Sstevel@tonic-gate * or http://www.opensolaris.org/os/licensing.
100Sstevel@tonic-gate * See the License for the specific language governing permissions
110Sstevel@tonic-gate * and limitations under the License.
120Sstevel@tonic-gate *
130Sstevel@tonic-gate * When distributing Covered Code, include this CDDL HEADER in each
140Sstevel@tonic-gate * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
150Sstevel@tonic-gate * If applicable, add the following below this CDDL HEADER, with the
160Sstevel@tonic-gate * fields enclosed by brackets "[]" replaced with your own identifying
170Sstevel@tonic-gate * information: Portions Copyright [yyyy] [name of copyright owner]
180Sstevel@tonic-gate *
190Sstevel@tonic-gate * CDDL HEADER END
200Sstevel@tonic-gate */
210Sstevel@tonic-gate /*
22*12172SSean.Wilcox@Sun.COM * Copyright (c) 2004, 2010, Oracle and/or its affiliates. All rights reserved.
230Sstevel@tonic-gate */
240Sstevel@tonic-gate
250Sstevel@tonic-gate
260Sstevel@tonic-gate #include <sys/stat.h>
270Sstevel@tonic-gate #include <sys/types.h>
280Sstevel@tonic-gate
290Sstevel@tonic-gate #include <assert.h>
300Sstevel@tonic-gate #include <ctype.h>
310Sstevel@tonic-gate #include <errno.h>
321061Scasper #include <fcntl.h>
330Sstevel@tonic-gate #include <libintl.h>
340Sstevel@tonic-gate #include <libscf.h>
350Sstevel@tonic-gate #include <libuutil.h>
360Sstevel@tonic-gate #include <limits.h>
370Sstevel@tonic-gate #include <md5.h>
380Sstevel@tonic-gate #include <pthread.h>
390Sstevel@tonic-gate #include <stdio.h>
400Sstevel@tonic-gate #include <stdlib.h>
410Sstevel@tonic-gate #include <string.h>
420Sstevel@tonic-gate #include <strings.h>
431061Scasper #include <unistd.h>
440Sstevel@tonic-gate
450Sstevel@tonic-gate #include <manifest_hash.h>
460Sstevel@tonic-gate
470Sstevel@tonic-gate /*
480Sstevel@tonic-gate * Translate a file name to property name. Return an allocated string or NULL
497475SPhilippe.Jung@Sun.COM * if realpath() fails. If deathrow is true, realpath() is skipped. This
507475SPhilippe.Jung@Sun.COM * allows to return the property name even if the file doesn't exist.
510Sstevel@tonic-gate */
520Sstevel@tonic-gate char *
mhash_filename_to_propname(const char * in,boolean_t deathrow)537475SPhilippe.Jung@Sun.COM mhash_filename_to_propname(const char *in, boolean_t deathrow)
540Sstevel@tonic-gate {
550Sstevel@tonic-gate char *out, *cp, *base;
560Sstevel@tonic-gate size_t len, piece_len;
57*12172SSean.Wilcox@Sun.COM size_t base_sz = 0;
580Sstevel@tonic-gate
590Sstevel@tonic-gate out = uu_zalloc(PATH_MAX + 1);
607475SPhilippe.Jung@Sun.COM if (deathrow) {
617475SPhilippe.Jung@Sun.COM /* used only for service deathrow handling */
627475SPhilippe.Jung@Sun.COM if (strlcpy(out, in, PATH_MAX + 1) >= (PATH_MAX + 1)) {
637475SPhilippe.Jung@Sun.COM uu_free(out);
647475SPhilippe.Jung@Sun.COM return (NULL);
657475SPhilippe.Jung@Sun.COM }
667475SPhilippe.Jung@Sun.COM } else {
677475SPhilippe.Jung@Sun.COM if (realpath(in, out) == NULL) {
687475SPhilippe.Jung@Sun.COM uu_free(out);
697475SPhilippe.Jung@Sun.COM return (NULL);
707475SPhilippe.Jung@Sun.COM }
710Sstevel@tonic-gate }
720Sstevel@tonic-gate
730Sstevel@tonic-gate base = getenv("PKG_INSTALL_ROOT");
740Sstevel@tonic-gate
750Sstevel@tonic-gate /*
760Sstevel@tonic-gate * We copy-shift over the basedir and the leading slash, since it's
770Sstevel@tonic-gate * not relevant to when we boot with this repository.
780Sstevel@tonic-gate */
790Sstevel@tonic-gate
80*12172SSean.Wilcox@Sun.COM if (base != NULL && strncmp(out, base, strlen(base)) == 0)
81*12172SSean.Wilcox@Sun.COM base_sz = strlen(base);
82*12172SSean.Wilcox@Sun.COM
83*12172SSean.Wilcox@Sun.COM cp = out + base_sz;
840Sstevel@tonic-gate if (*cp == '/')
850Sstevel@tonic-gate cp++;
860Sstevel@tonic-gate (void) memmove(out, cp, strlen(cp) + 1);
870Sstevel@tonic-gate
880Sstevel@tonic-gate len = strlen(out);
890Sstevel@tonic-gate if (len > scf_limit(SCF_LIMIT_MAX_NAME_LENGTH)) {
900Sstevel@tonic-gate /* Use the first half and the second half. */
910Sstevel@tonic-gate piece_len = (scf_limit(SCF_LIMIT_MAX_NAME_LENGTH) - 3) / 2;
920Sstevel@tonic-gate
930Sstevel@tonic-gate (void) strncpy(out + piece_len, "__", 2);
940Sstevel@tonic-gate
950Sstevel@tonic-gate (void) memmove(out + piece_len + 2, out + (len - piece_len),
960Sstevel@tonic-gate piece_len + 1);
970Sstevel@tonic-gate }
980Sstevel@tonic-gate
990Sstevel@tonic-gate /*
1000Sstevel@tonic-gate * Translate non-property characters to '_', first making sure that
1010Sstevel@tonic-gate * we don't begin with '_'.
1020Sstevel@tonic-gate */
1030Sstevel@tonic-gate
1040Sstevel@tonic-gate if (!isalpha(*out))
1050Sstevel@tonic-gate *out = 'A';
1060Sstevel@tonic-gate
1070Sstevel@tonic-gate for (cp = out + 1; *cp != '\0'; ++cp) {
1080Sstevel@tonic-gate if (!(isalnum(*cp) || *cp == '_' || *cp == '-'))
1090Sstevel@tonic-gate *cp = '_';
1100Sstevel@tonic-gate }
1110Sstevel@tonic-gate
1120Sstevel@tonic-gate return (out);
1130Sstevel@tonic-gate }
1140Sstevel@tonic-gate
1150Sstevel@tonic-gate int
mhash_retrieve_entry(scf_handle_t * hndl,const char * name,uchar_t * hash,apply_action_t * action)11611996SThomas.Whitten@Sun.COM mhash_retrieve_entry(scf_handle_t *hndl, const char *name, uchar_t *hash,
11711996SThomas.Whitten@Sun.COM apply_action_t *action)
1180Sstevel@tonic-gate {
1190Sstevel@tonic-gate scf_scope_t *scope;
1200Sstevel@tonic-gate scf_service_t *svc;
1210Sstevel@tonic-gate scf_propertygroup_t *pg;
1220Sstevel@tonic-gate scf_property_t *prop;
1230Sstevel@tonic-gate scf_value_t *val;
12411996SThomas.Whitten@Sun.COM scf_error_t err;
1250Sstevel@tonic-gate ssize_t szret;
1260Sstevel@tonic-gate int result = 0;
1270Sstevel@tonic-gate
12811996SThomas.Whitten@Sun.COM if (action)
12911996SThomas.Whitten@Sun.COM *action = APPLY_NONE;
13011996SThomas.Whitten@Sun.COM
1310Sstevel@tonic-gate /*
1320Sstevel@tonic-gate * In this implementation the hash for name is the opaque value of
1330Sstevel@tonic-gate * svc:/MHASH_SVC/:properties/name/MHASH_PROP
1340Sstevel@tonic-gate */
1350Sstevel@tonic-gate
1360Sstevel@tonic-gate if ((scope = scf_scope_create(hndl)) == NULL ||
1370Sstevel@tonic-gate (svc = scf_service_create(hndl)) == NULL ||
1380Sstevel@tonic-gate (pg = scf_pg_create(hndl)) == NULL ||
1390Sstevel@tonic-gate (prop = scf_property_create(hndl)) == NULL ||
1400Sstevel@tonic-gate (val = scf_value_create(hndl)) == NULL) {
1410Sstevel@tonic-gate result = -1;
1420Sstevel@tonic-gate goto out;
1430Sstevel@tonic-gate }
1440Sstevel@tonic-gate
1450Sstevel@tonic-gate if (scf_handle_get_local_scope(hndl, scope) < 0) {
1460Sstevel@tonic-gate result = -1;
1470Sstevel@tonic-gate goto out;
1480Sstevel@tonic-gate }
1490Sstevel@tonic-gate
1500Sstevel@tonic-gate if (scf_scope_get_service(scope, MHASH_SVC, svc) < 0) {
1510Sstevel@tonic-gate result = -1;
1520Sstevel@tonic-gate goto out;
1530Sstevel@tonic-gate }
1540Sstevel@tonic-gate
1550Sstevel@tonic-gate if (scf_service_get_pg(svc, name, pg) != SCF_SUCCESS) {
1560Sstevel@tonic-gate result = -1;
1570Sstevel@tonic-gate goto out;
1580Sstevel@tonic-gate }
1590Sstevel@tonic-gate
1600Sstevel@tonic-gate if (scf_pg_get_property(pg, MHASH_PROP, prop) != SCF_SUCCESS) {
1610Sstevel@tonic-gate result = -1;
1620Sstevel@tonic-gate goto out;
1630Sstevel@tonic-gate }
1640Sstevel@tonic-gate
1650Sstevel@tonic-gate if (scf_property_get_value(prop, val) != SCF_SUCCESS) {
1660Sstevel@tonic-gate result = -1;
1670Sstevel@tonic-gate goto out;
1680Sstevel@tonic-gate }
1690Sstevel@tonic-gate
1700Sstevel@tonic-gate szret = scf_value_get_opaque(val, hash, MHASH_SIZE);
1710Sstevel@tonic-gate if (szret < 0) {
1720Sstevel@tonic-gate result = -1;
1730Sstevel@tonic-gate goto out;
1740Sstevel@tonic-gate }
1750Sstevel@tonic-gate
1761061Scasper /*
1771061Scasper * Make sure that the old hash is returned with
1781061Scasper * remainder of the bytes zeroed.
1791061Scasper */
1801061Scasper if (szret == MHASH_SIZE_OLD) {
1811061Scasper (void) memset(hash + MHASH_SIZE_OLD, 0,
1821061Scasper MHASH_SIZE - MHASH_SIZE_OLD);
1831061Scasper } else if (szret != MHASH_SIZE) {
1840Sstevel@tonic-gate scf_value_destroy(val);
1850Sstevel@tonic-gate result = -1;
1860Sstevel@tonic-gate goto out;
1870Sstevel@tonic-gate }
1880Sstevel@tonic-gate
18911996SThomas.Whitten@Sun.COM /*
19011996SThomas.Whitten@Sun.COM * If caller has requested the apply_last property, read the
19111996SThomas.Whitten@Sun.COM * property if it exists.
19211996SThomas.Whitten@Sun.COM */
19311996SThomas.Whitten@Sun.COM if (action != NULL) {
19411996SThomas.Whitten@Sun.COM uint8_t apply_value;
19511996SThomas.Whitten@Sun.COM
19611996SThomas.Whitten@Sun.COM if (scf_pg_get_property(pg, MHASH_APPLY_PROP, prop) !=
19711996SThomas.Whitten@Sun.COM SCF_SUCCESS) {
19811996SThomas.Whitten@Sun.COM err = scf_error();
19911996SThomas.Whitten@Sun.COM if ((err != SCF_ERROR_DELETED) &&
20011996SThomas.Whitten@Sun.COM (err != SCF_ERROR_NOT_FOUND)) {
20111996SThomas.Whitten@Sun.COM result = -1;
20211996SThomas.Whitten@Sun.COM }
20311996SThomas.Whitten@Sun.COM goto out;
20411996SThomas.Whitten@Sun.COM }
20511996SThomas.Whitten@Sun.COM if (scf_property_get_value(prop, val) != SCF_SUCCESS) {
20611996SThomas.Whitten@Sun.COM err = scf_error();
20711996SThomas.Whitten@Sun.COM if ((err != SCF_ERROR_DELETED) &&
20811996SThomas.Whitten@Sun.COM (err != SCF_ERROR_NOT_FOUND)) {
20911996SThomas.Whitten@Sun.COM result = -1;
21011996SThomas.Whitten@Sun.COM }
21111996SThomas.Whitten@Sun.COM goto out;
21211996SThomas.Whitten@Sun.COM }
21311996SThomas.Whitten@Sun.COM if (scf_value_get_boolean(val, &apply_value) != SCF_SUCCESS) {
21411996SThomas.Whitten@Sun.COM result = -1;
21511996SThomas.Whitten@Sun.COM goto out;
21611996SThomas.Whitten@Sun.COM }
21711996SThomas.Whitten@Sun.COM if (apply_value)
21811996SThomas.Whitten@Sun.COM *action = APPLY_LATE;
21911996SThomas.Whitten@Sun.COM }
22011996SThomas.Whitten@Sun.COM
2210Sstevel@tonic-gate out:
2220Sstevel@tonic-gate (void) scf_value_destroy(val);
2230Sstevel@tonic-gate scf_property_destroy(prop);
2240Sstevel@tonic-gate scf_pg_destroy(pg);
2250Sstevel@tonic-gate scf_service_destroy(svc);
2260Sstevel@tonic-gate scf_scope_destroy(scope);
2270Sstevel@tonic-gate
2280Sstevel@tonic-gate return (result);
2290Sstevel@tonic-gate }
2300Sstevel@tonic-gate
2310Sstevel@tonic-gate int
mhash_store_entry(scf_handle_t * hndl,const char * name,const char * fname,uchar_t * hash,apply_action_t apply_late,char ** errstr)23210461SSean.Wilcox@Sun.COM mhash_store_entry(scf_handle_t *hndl, const char *name, const char *fname,
23311996SThomas.Whitten@Sun.COM uchar_t *hash, apply_action_t apply_late, char **errstr)
2340Sstevel@tonic-gate {
2350Sstevel@tonic-gate scf_scope_t *scope = NULL;
2360Sstevel@tonic-gate scf_service_t *svc = NULL;
2370Sstevel@tonic-gate scf_propertygroup_t *pg = NULL;
2380Sstevel@tonic-gate scf_property_t *prop = NULL;
23911996SThomas.Whitten@Sun.COM scf_value_t *aval = NULL;
2400Sstevel@tonic-gate scf_value_t *val = NULL;
24110461SSean.Wilcox@Sun.COM scf_value_t *fval = NULL;
2420Sstevel@tonic-gate scf_transaction_t *tx = NULL;
24311996SThomas.Whitten@Sun.COM scf_transaction_entry_t *ae = NULL;
2440Sstevel@tonic-gate scf_transaction_entry_t *e = NULL;
24510461SSean.Wilcox@Sun.COM scf_transaction_entry_t *fe = NULL;
24611996SThomas.Whitten@Sun.COM scf_error_t err;
2470Sstevel@tonic-gate int ret, result = 0;
2480Sstevel@tonic-gate
2490Sstevel@tonic-gate int i;
2500Sstevel@tonic-gate
2510Sstevel@tonic-gate if ((scope = scf_scope_create(hndl)) == NULL ||
2520Sstevel@tonic-gate (svc = scf_service_create(hndl)) == NULL ||
2530Sstevel@tonic-gate (pg = scf_pg_create(hndl)) == NULL ||
2540Sstevel@tonic-gate (prop = scf_property_create(hndl)) == NULL) {
2550Sstevel@tonic-gate if (errstr != NULL)
2560Sstevel@tonic-gate *errstr = gettext("Could not create scf objects");
2570Sstevel@tonic-gate result = -1;
2580Sstevel@tonic-gate goto out;
2590Sstevel@tonic-gate }
2600Sstevel@tonic-gate
2610Sstevel@tonic-gate if (scf_handle_get_local_scope(hndl, scope) != SCF_SUCCESS) {
2620Sstevel@tonic-gate if (errstr != NULL)
2630Sstevel@tonic-gate *errstr = gettext("Could not get local scope");
2640Sstevel@tonic-gate result = -1;
2650Sstevel@tonic-gate goto out;
2660Sstevel@tonic-gate }
2670Sstevel@tonic-gate
2680Sstevel@tonic-gate for (i = 0; i < 5; ++i) {
2690Sstevel@tonic-gate
2700Sstevel@tonic-gate if (scf_scope_get_service(scope, MHASH_SVC, svc) ==
2710Sstevel@tonic-gate SCF_SUCCESS)
2720Sstevel@tonic-gate break;
2730Sstevel@tonic-gate
2740Sstevel@tonic-gate if (scf_error() != SCF_ERROR_NOT_FOUND) {
2750Sstevel@tonic-gate if (errstr != NULL)
2760Sstevel@tonic-gate *errstr = gettext("Could not get manifest hash "
2770Sstevel@tonic-gate "service");
2780Sstevel@tonic-gate result = -1;
2790Sstevel@tonic-gate goto out;
2800Sstevel@tonic-gate }
2810Sstevel@tonic-gate
2820Sstevel@tonic-gate if (scf_scope_add_service(scope, MHASH_SVC, svc) ==
2830Sstevel@tonic-gate SCF_SUCCESS)
2840Sstevel@tonic-gate break;
2850Sstevel@tonic-gate
2860Sstevel@tonic-gate err = scf_error();
2870Sstevel@tonic-gate
2880Sstevel@tonic-gate if (err == SCF_ERROR_EXISTS)
2890Sstevel@tonic-gate /* Try again. */
2900Sstevel@tonic-gate continue;
2910Sstevel@tonic-gate else if (err == SCF_ERROR_PERMISSION_DENIED) {
2920Sstevel@tonic-gate if (errstr != NULL)
2930Sstevel@tonic-gate *errstr = gettext("Could not store file hash: "
2940Sstevel@tonic-gate "permission denied.\n");
2950Sstevel@tonic-gate result = -1;
2960Sstevel@tonic-gate goto out;
2970Sstevel@tonic-gate }
2980Sstevel@tonic-gate
2990Sstevel@tonic-gate if (errstr != NULL)
3000Sstevel@tonic-gate *errstr = gettext("Could not add manifest hash "
3010Sstevel@tonic-gate "service");
3020Sstevel@tonic-gate result = -1;
3030Sstevel@tonic-gate goto out;
3040Sstevel@tonic-gate }
3050Sstevel@tonic-gate
3060Sstevel@tonic-gate if (i == 5) {
3070Sstevel@tonic-gate if (errstr != NULL)
3080Sstevel@tonic-gate *errstr = gettext("Could not store file hash: "
3090Sstevel@tonic-gate "service addition contention.\n");
3100Sstevel@tonic-gate result = -1;
3110Sstevel@tonic-gate goto out;
3120Sstevel@tonic-gate }
3130Sstevel@tonic-gate
3140Sstevel@tonic-gate for (i = 0; i < 5; ++i) {
3150Sstevel@tonic-gate if (scf_service_get_pg(svc, name, pg) == SCF_SUCCESS)
3160Sstevel@tonic-gate break;
3170Sstevel@tonic-gate
3180Sstevel@tonic-gate if (scf_error() != SCF_ERROR_NOT_FOUND) {
3190Sstevel@tonic-gate if (errstr != NULL)
3200Sstevel@tonic-gate *errstr = gettext("Could not get service's "
3210Sstevel@tonic-gate "hash record)");
3220Sstevel@tonic-gate result = -1;
3230Sstevel@tonic-gate goto out;
3240Sstevel@tonic-gate }
3250Sstevel@tonic-gate
3260Sstevel@tonic-gate if (scf_service_add_pg(svc, name, MHASH_PG_TYPE,
3270Sstevel@tonic-gate MHASH_PG_FLAGS, pg) == SCF_SUCCESS)
3280Sstevel@tonic-gate break;
3290Sstevel@tonic-gate
3300Sstevel@tonic-gate err = scf_error();
3310Sstevel@tonic-gate
3320Sstevel@tonic-gate if (err == SCF_ERROR_EXISTS)
3330Sstevel@tonic-gate /* Try again. */
3340Sstevel@tonic-gate continue;
3350Sstevel@tonic-gate else if (err == SCF_ERROR_PERMISSION_DENIED) {
3360Sstevel@tonic-gate if (errstr != NULL)
3370Sstevel@tonic-gate *errstr = gettext("Could not store file hash: "
3380Sstevel@tonic-gate "permission denied.\n");
3390Sstevel@tonic-gate result = -1;
3400Sstevel@tonic-gate goto out;
3410Sstevel@tonic-gate }
3420Sstevel@tonic-gate
3430Sstevel@tonic-gate if (errstr != NULL)
3440Sstevel@tonic-gate *errstr = gettext("Could not store file hash");
3450Sstevel@tonic-gate result = -1;
3460Sstevel@tonic-gate goto out;
3470Sstevel@tonic-gate }
3480Sstevel@tonic-gate if (i == 5) {
3490Sstevel@tonic-gate if (errstr != NULL)
3500Sstevel@tonic-gate *errstr = gettext("Could not store file hash: "
3510Sstevel@tonic-gate "property group addition contention.\n");
3520Sstevel@tonic-gate result = -1;
3530Sstevel@tonic-gate goto out;
3540Sstevel@tonic-gate }
3550Sstevel@tonic-gate
3560Sstevel@tonic-gate if ((e = scf_entry_create(hndl)) == NULL ||
35710461SSean.Wilcox@Sun.COM (val = scf_value_create(hndl)) == NULL ||
35810461SSean.Wilcox@Sun.COM (fe = scf_entry_create(hndl)) == NULL ||
35911996SThomas.Whitten@Sun.COM (fval = scf_value_create(hndl)) == NULL ||
36011996SThomas.Whitten@Sun.COM (ae = scf_entry_create(hndl)) == NULL ||
36111996SThomas.Whitten@Sun.COM (aval = scf_value_create(hndl)) == NULL) {
3620Sstevel@tonic-gate if (errstr != NULL)
3630Sstevel@tonic-gate *errstr = gettext("Could not store file hash: "
3640Sstevel@tonic-gate "permission denied.\n");
3650Sstevel@tonic-gate result = -1;
3660Sstevel@tonic-gate goto out;
3670Sstevel@tonic-gate }
3680Sstevel@tonic-gate
3690Sstevel@tonic-gate ret = scf_value_set_opaque(val, hash, MHASH_SIZE);
3700Sstevel@tonic-gate assert(ret == SCF_SUCCESS);
37110461SSean.Wilcox@Sun.COM ret = scf_value_set_astring(fval, fname);
37210461SSean.Wilcox@Sun.COM assert(ret == SCF_SUCCESS);
37311996SThomas.Whitten@Sun.COM if (apply_late == APPLY_LATE) {
37411996SThomas.Whitten@Sun.COM scf_value_set_boolean(aval, 1);
37511996SThomas.Whitten@Sun.COM }
3760Sstevel@tonic-gate
3770Sstevel@tonic-gate tx = scf_transaction_create(hndl);
3780Sstevel@tonic-gate if (tx == NULL) {
3790Sstevel@tonic-gate if (errstr != NULL)
3800Sstevel@tonic-gate *errstr = gettext("Could not create transaction");
3810Sstevel@tonic-gate result = -1;
3820Sstevel@tonic-gate goto out;
3830Sstevel@tonic-gate }
3840Sstevel@tonic-gate
3850Sstevel@tonic-gate do {
3860Sstevel@tonic-gate if (scf_pg_update(pg) == -1) {
3870Sstevel@tonic-gate if (errstr != NULL)
3880Sstevel@tonic-gate *errstr = gettext("Could not update hash "
3890Sstevel@tonic-gate "entry");
3900Sstevel@tonic-gate result = -1;
3910Sstevel@tonic-gate goto out;
3920Sstevel@tonic-gate }
3930Sstevel@tonic-gate if (scf_transaction_start(tx, pg) != SCF_SUCCESS) {
3940Sstevel@tonic-gate if (scf_error() != SCF_ERROR_PERMISSION_DENIED) {
3950Sstevel@tonic-gate if (errstr != NULL)
3960Sstevel@tonic-gate *errstr = gettext("Could not start "
3970Sstevel@tonic-gate "hash transaction.\n");
3980Sstevel@tonic-gate result = -1;
3990Sstevel@tonic-gate goto out;
4000Sstevel@tonic-gate }
4010Sstevel@tonic-gate
4020Sstevel@tonic-gate if (errstr != NULL)
4030Sstevel@tonic-gate *errstr = gettext("Could not store file hash: "
4040Sstevel@tonic-gate "permission denied.\n");
4050Sstevel@tonic-gate result = -1;
4060Sstevel@tonic-gate
4070Sstevel@tonic-gate scf_transaction_destroy(tx);
4080Sstevel@tonic-gate (void) scf_entry_destroy(e);
4090Sstevel@tonic-gate goto out;
4100Sstevel@tonic-gate }
4110Sstevel@tonic-gate
4120Sstevel@tonic-gate if (scf_transaction_property_new(tx, e, MHASH_PROP,
4130Sstevel@tonic-gate SCF_TYPE_OPAQUE) != SCF_SUCCESS &&
4140Sstevel@tonic-gate scf_transaction_property_change_type(tx, e, MHASH_PROP,
4150Sstevel@tonic-gate SCF_TYPE_OPAQUE) != SCF_SUCCESS) {
4160Sstevel@tonic-gate if (errstr != NULL)
4170Sstevel@tonic-gate *errstr = gettext("Could not modify hash "
4180Sstevel@tonic-gate "entry");
4190Sstevel@tonic-gate result = -1;
4200Sstevel@tonic-gate goto out;
4210Sstevel@tonic-gate }
4220Sstevel@tonic-gate
4230Sstevel@tonic-gate ret = scf_entry_add_value(e, val);
4240Sstevel@tonic-gate assert(ret == SCF_SUCCESS);
4250Sstevel@tonic-gate
42611996SThomas.Whitten@Sun.COM if (scf_transaction_property_new(tx, fe, MHASH_FILE_PROP,
42710461SSean.Wilcox@Sun.COM SCF_TYPE_ASTRING) != SCF_SUCCESS &&
42811996SThomas.Whitten@Sun.COM scf_transaction_property_change_type(tx, fe,
42911996SThomas.Whitten@Sun.COM MHASH_FILE_PROP, SCF_TYPE_ASTRING) != SCF_SUCCESS) {
43010461SSean.Wilcox@Sun.COM if (errstr != NULL)
43110461SSean.Wilcox@Sun.COM *errstr = gettext("Could not modify file "
43210461SSean.Wilcox@Sun.COM "entry");
43310461SSean.Wilcox@Sun.COM result = -1;
43410461SSean.Wilcox@Sun.COM goto out;
43510461SSean.Wilcox@Sun.COM }
43610461SSean.Wilcox@Sun.COM
43710461SSean.Wilcox@Sun.COM ret = scf_entry_add_value(fe, fval);
43810461SSean.Wilcox@Sun.COM assert(ret == SCF_SUCCESS);
43910461SSean.Wilcox@Sun.COM
44011996SThomas.Whitten@Sun.COM switch (apply_late) {
44111996SThomas.Whitten@Sun.COM case APPLY_NONE:
44211996SThomas.Whitten@Sun.COM if (scf_transaction_property_delete(tx, ae,
44311996SThomas.Whitten@Sun.COM MHASH_APPLY_PROP) != 0) {
44411996SThomas.Whitten@Sun.COM err = scf_error();
44511996SThomas.Whitten@Sun.COM if ((err != SCF_ERROR_DELETED) &&
44611996SThomas.Whitten@Sun.COM (err != SCF_ERROR_NOT_FOUND)) {
44711996SThomas.Whitten@Sun.COM if (errstr != NULL) {
44811996SThomas.Whitten@Sun.COM *errstr = gettext("Could not "
44911996SThomas.Whitten@Sun.COM "delete apply_late "
45011996SThomas.Whitten@Sun.COM "property");
45111996SThomas.Whitten@Sun.COM }
45211996SThomas.Whitten@Sun.COM result = -1;
45311996SThomas.Whitten@Sun.COM goto out;
45411996SThomas.Whitten@Sun.COM }
45511996SThomas.Whitten@Sun.COM }
45611996SThomas.Whitten@Sun.COM break;
45711996SThomas.Whitten@Sun.COM case APPLY_LATE:
45811996SThomas.Whitten@Sun.COM if ((scf_transaction_property_new(tx, ae,
45911996SThomas.Whitten@Sun.COM MHASH_APPLY_PROP,
46011996SThomas.Whitten@Sun.COM SCF_TYPE_BOOLEAN) != SCF_SUCCESS) &&
46111996SThomas.Whitten@Sun.COM (scf_transaction_property_change_type(tx, ae,
46211996SThomas.Whitten@Sun.COM MHASH_APPLY_PROP, SCF_TYPE_BOOLEAN) !=
46311996SThomas.Whitten@Sun.COM SCF_SUCCESS)) {
46411996SThomas.Whitten@Sun.COM if (errstr != NULL) {
46511996SThomas.Whitten@Sun.COM *errstr = gettext("Could not modify "
46611996SThomas.Whitten@Sun.COM "apply_late property");
46711996SThomas.Whitten@Sun.COM }
46811996SThomas.Whitten@Sun.COM result = -1;
46911996SThomas.Whitten@Sun.COM goto out;
47011996SThomas.Whitten@Sun.COM }
47111996SThomas.Whitten@Sun.COM
47211996SThomas.Whitten@Sun.COM ret = scf_entry_add_value(ae, aval);
47311996SThomas.Whitten@Sun.COM assert(ret == SCF_SUCCESS);
47411996SThomas.Whitten@Sun.COM break;
47511996SThomas.Whitten@Sun.COM default:
47611996SThomas.Whitten@Sun.COM abort();
47711996SThomas.Whitten@Sun.COM };
47811996SThomas.Whitten@Sun.COM
4790Sstevel@tonic-gate ret = scf_transaction_commit(tx);
4800Sstevel@tonic-gate
4810Sstevel@tonic-gate if (ret == 0)
4820Sstevel@tonic-gate scf_transaction_reset(tx);
4830Sstevel@tonic-gate } while (ret == 0);
4840Sstevel@tonic-gate
4850Sstevel@tonic-gate if (ret < 0) {
4860Sstevel@tonic-gate if (scf_error() != SCF_ERROR_PERMISSION_DENIED) {
4870Sstevel@tonic-gate if (errstr != NULL)
4880Sstevel@tonic-gate *errstr = gettext("Could not store file hash: "
4890Sstevel@tonic-gate "permission denied.\n");
4900Sstevel@tonic-gate result = -1;
4910Sstevel@tonic-gate goto out;
4920Sstevel@tonic-gate }
4930Sstevel@tonic-gate
4940Sstevel@tonic-gate if (errstr != NULL)
4950Sstevel@tonic-gate *errstr = gettext("Could not commit transaction");
4960Sstevel@tonic-gate result = -1;
4970Sstevel@tonic-gate }
4980Sstevel@tonic-gate
4990Sstevel@tonic-gate scf_transaction_destroy(tx);
5000Sstevel@tonic-gate (void) scf_entry_destroy(e);
50110461SSean.Wilcox@Sun.COM (void) scf_entry_destroy(fe);
50211996SThomas.Whitten@Sun.COM (void) scf_entry_destroy(ae);
5030Sstevel@tonic-gate
5040Sstevel@tonic-gate out:
5050Sstevel@tonic-gate (void) scf_value_destroy(val);
50610461SSean.Wilcox@Sun.COM (void) scf_value_destroy(fval);
50711996SThomas.Whitten@Sun.COM (void) scf_value_destroy(aval);
5080Sstevel@tonic-gate scf_property_destroy(prop);
5090Sstevel@tonic-gate scf_pg_destroy(pg);
5100Sstevel@tonic-gate scf_service_destroy(svc);
5110Sstevel@tonic-gate scf_scope_destroy(scope);
5120Sstevel@tonic-gate
5130Sstevel@tonic-gate return (result);
5140Sstevel@tonic-gate }
5150Sstevel@tonic-gate
5160Sstevel@tonic-gate /*
5171061Scasper * Generate the md5 hash of a file; manifest files are smallish
5181061Scasper * so we can read them in one gulp.
5191061Scasper */
5201061Scasper static int
md5_hash_file(const char * file,off64_t sz,uchar_t * hash)5211061Scasper md5_hash_file(const char *file, off64_t sz, uchar_t *hash)
5221061Scasper {
5231061Scasper char *buf;
5241061Scasper int fd;
5251061Scasper ssize_t res;
5261061Scasper int ret;
5271061Scasper
5281061Scasper fd = open(file, O_RDONLY);
5291061Scasper if (fd < 0)
5301061Scasper return (-1);
5311061Scasper
5321061Scasper buf = malloc(sz);
5331061Scasper if (buf == NULL) {
5341061Scasper (void) close(fd);
5351061Scasper return (-1);
5361061Scasper }
5371061Scasper
5381061Scasper res = read(fd, buf, (size_t)sz);
5391061Scasper
5401061Scasper (void) close(fd);
5411061Scasper
5421061Scasper if (res == sz) {
5431061Scasper ret = 0;
5441061Scasper md5_calc(hash, (uchar_t *)buf, (unsigned int) sz);
5451061Scasper } else {
5461061Scasper ret = -1;
5471061Scasper }
5481061Scasper
5491061Scasper free(buf);
5501061Scasper return (ret);
5511061Scasper }
5521061Scasper
5531061Scasper /*
5540Sstevel@tonic-gate * int mhash_test_file(scf_handle_t *, const char *, uint_t, char **, uchar_t *)
5550Sstevel@tonic-gate * Test the given filename against the hashed metadata in the repository.
5560Sstevel@tonic-gate * The behaviours for import and apply are slightly different. For imports,
5570Sstevel@tonic-gate * if the hash value is absent or different, then the import operation
5580Sstevel@tonic-gate * continues. For profile application, the operation continues only if the
5590Sstevel@tonic-gate * hash value for the file is absent.
5600Sstevel@tonic-gate *
5612462Scasper * We keep two hashes: one which can be quickly test: the metadata hash,
5622462Scasper * and one which is more expensive to test: the file contents hash.
5632462Scasper *
5642462Scasper * If either hash matches, the file does not need to be re-read.
5652462Scasper * If only one of the hashes matches, a side effect of this function
5662462Scasper * is to store the newly computed hash.
5672462Scasper * If neither hash matches, the hash computed for the new file is returned
5682462Scasper * and not stored.
5692462Scasper *
5702462Scasper * Return values:
5712462Scasper * MHASH_NEWFILE - the file no longer matches the hash or no hash existed
5722462Scasper * ONLY in this case we return the new file's hash.
5732462Scasper * MHASH_FAILURE - an internal error occurred, or the file was not found.
5742462Scasper * MHASH_RECONCILED- based on the metadata/file hash, the file does
5752462Scasper * not need to be re-read; if necessary,
5762462Scasper * the hash was upgraded or reconciled.
5772462Scasper *
5782462Scasper * NOTE: no hash is returned UNLESS MHASH_NEWFILE is returned.
5790Sstevel@tonic-gate */
5800Sstevel@tonic-gate int
mhash_test_file(scf_handle_t * hndl,const char * file,uint_t is_profile,char ** pnamep,uchar_t * hashbuf)5810Sstevel@tonic-gate mhash_test_file(scf_handle_t *hndl, const char *file, uint_t is_profile,
5822462Scasper char **pnamep, uchar_t *hashbuf)
5830Sstevel@tonic-gate {
58411996SThomas.Whitten@Sun.COM apply_action_t action;
5850Sstevel@tonic-gate boolean_t do_hash;
5860Sstevel@tonic-gate struct stat64 st;
5870Sstevel@tonic-gate char *cp;
5880Sstevel@tonic-gate char *data;
5890Sstevel@tonic-gate uchar_t stored_hash[MHASH_SIZE];
5902462Scasper uchar_t hash[MHASH_SIZE];
5910Sstevel@tonic-gate char *pname;
5920Sstevel@tonic-gate int ret;
5932462Scasper int hashash;
5942462Scasper int metahashok = 0;
5950Sstevel@tonic-gate
59611996SThomas.Whitten@Sun.COM if (pnamep)
59711996SThomas.Whitten@Sun.COM *pnamep = NULL;
59811996SThomas.Whitten@Sun.COM
5990Sstevel@tonic-gate /*
6000Sstevel@tonic-gate * In the case where we are doing automated imports, we reduce the UID,
6010Sstevel@tonic-gate * the GID, the size, and the mtime into a string (to eliminate
6020Sstevel@tonic-gate * endianness) which we then make opaque as a single MD5 digest.
6030Sstevel@tonic-gate *
6040Sstevel@tonic-gate * The previous hash was composed of the inode number, the UID, the file
6050Sstevel@tonic-gate * size, and the mtime. This formulation was found to be insufficiently
6060Sstevel@tonic-gate * portable for use in highly replicated deployments. The current
6070Sstevel@tonic-gate * algorithm will allow matches of this "v1" hash, but always returns
6080Sstevel@tonic-gate * the effective "v2" hash, such that updates result in the more
6090Sstevel@tonic-gate * portable hash being used.
6100Sstevel@tonic-gate *
6111061Scasper * An unwanted side effect of a hash based solely on the file
6121061Scasper * meta data is the fact that we pay no attention to the contents
6131061Scasper * which may remain the same despite meta data changes. This happens
6141061Scasper * with (live) upgrades. We extend the V2 hash with an additional
6151061Scasper * digest of the file contents and the code retrieving the hash
6161061Scasper * from the repository zero fills the remainder so we can detect
6171061Scasper * it is missing.
6181061Scasper *
6191061Scasper * If the the V2 digest matches, we check for the presence of
6201061Scasper * the contents digest and compute and store it if missing.
6211061Scasper *
6221061Scasper * If the V2 digest doesn't match but we also have a non-zero
6231061Scasper * file hash, we match the file content digest. If it matches,
6241061Scasper * we compute and store the new complete hash so that later
6251061Scasper * checks will find the meta data digest correct.
6261061Scasper *
6271061Scasper * If the above matches fail and the V1 hash doesn't match either,
6281061Scasper * we consider the test to have failed, implying that some aspect
6291061Scasper * of the manifest has changed.
6300Sstevel@tonic-gate */
6310Sstevel@tonic-gate
6320Sstevel@tonic-gate cp = getenv("SVCCFG_CHECKHASH");
6330Sstevel@tonic-gate do_hash = (cp != NULL && *cp != '\0');
6340Sstevel@tonic-gate if (!do_hash) {
6352462Scasper return (MHASH_NEWFILE);
6362462Scasper }
6372462Scasper
6387475SPhilippe.Jung@Sun.COM pname = mhash_filename_to_propname(file, B_FALSE);
6392462Scasper if (pname == NULL)
6402462Scasper return (MHASH_FAILURE);
6412462Scasper
64211996SThomas.Whitten@Sun.COM hashash = mhash_retrieve_entry(hndl, pname, stored_hash, &action) == 0;
64311996SThomas.Whitten@Sun.COM if (is_profile == 0) {
64411996SThomas.Whitten@Sun.COM /* Actions other than APPLY_NONE are restricted to profiles. */
64511996SThomas.Whitten@Sun.COM assert(action == APPLY_NONE);
64611996SThomas.Whitten@Sun.COM }
6472462Scasper
64811996SThomas.Whitten@Sun.COM /*
64911996SThomas.Whitten@Sun.COM * As a general rule, we do not reread a profile. The exception to
65011996SThomas.Whitten@Sun.COM * this rule is when we are running as part of the manifest import
65111996SThomas.Whitten@Sun.COM * service and the apply_late property is set to true.
65211996SThomas.Whitten@Sun.COM */
6532462Scasper if (hashash && is_profile) {
65411996SThomas.Whitten@Sun.COM cp = getenv("SMF_FMRI");
65511996SThomas.Whitten@Sun.COM if ((cp == NULL) ||
65611996SThomas.Whitten@Sun.COM (strcmp(cp, SCF_INSTANCE_MI) != 0) ||
65711996SThomas.Whitten@Sun.COM (action != APPLY_LATE)) {
65811996SThomas.Whitten@Sun.COM uu_free(pname);
65911996SThomas.Whitten@Sun.COM return (MHASH_RECONCILED);
66011996SThomas.Whitten@Sun.COM }
6612462Scasper }
6622462Scasper
6632462Scasper /*
6642462Scasper * No hash and not interested in one, then don't bother computing it.
6652462Scasper * We also skip returning the property name in that case.
6662462Scasper */
6672462Scasper if (!hashash && hashbuf == NULL) {
6682462Scasper uu_free(pname);
6692462Scasper return (MHASH_NEWFILE);
6700Sstevel@tonic-gate }
6710Sstevel@tonic-gate
6727475SPhilippe.Jung@Sun.COM do {
6730Sstevel@tonic-gate ret = stat64(file, &st);
6747475SPhilippe.Jung@Sun.COM } while (ret < 0 && errno == EINTR);
6750Sstevel@tonic-gate if (ret < 0) {
6762462Scasper uu_free(pname);
6772462Scasper return (MHASH_FAILURE);
6780Sstevel@tonic-gate }
6790Sstevel@tonic-gate
6800Sstevel@tonic-gate data = uu_msprintf(MHASH_FORMAT_V2, st.st_uid, st.st_gid,
6810Sstevel@tonic-gate st.st_size, st.st_mtime);
6820Sstevel@tonic-gate if (data == NULL) {
6832462Scasper uu_free(pname);
6842462Scasper return (MHASH_FAILURE);
6850Sstevel@tonic-gate }
6860Sstevel@tonic-gate
6871061Scasper (void) memset(hash, 0, MHASH_SIZE);
6880Sstevel@tonic-gate md5_calc(hash, (uchar_t *)data, strlen(data));
6890Sstevel@tonic-gate
6900Sstevel@tonic-gate uu_free(data);
6910Sstevel@tonic-gate
6922462Scasper /*
6932462Scasper * Verify the meta data hash.
6942462Scasper */
6952462Scasper if (hashash && memcmp(hash, stored_hash, MD5_DIGEST_LENGTH) == 0) {
6962462Scasper int i;
6972462Scasper
6982462Scasper metahashok = 1;
6992462Scasper /*
7002462Scasper * The metadata hash matches; now we see if there was a
7012462Scasper * content hash; if not, we will continue on and compute and
7022462Scasper * store the updated hash.
7032462Scasper * If there was no content hash, mhash_retrieve_entry()
7042462Scasper * will have zero filled it.
7052462Scasper */
7062462Scasper for (i = 0; i < MD5_DIGEST_LENGTH; i++) {
7072462Scasper if (stored_hash[MD5_DIGEST_LENGTH+i] != 0) {
70811996SThomas.Whitten@Sun.COM if (action == APPLY_LATE) {
70911996SThomas.Whitten@Sun.COM if (pnamep != NULL)
71011996SThomas.Whitten@Sun.COM *pnamep = pname;
71111996SThomas.Whitten@Sun.COM ret = MHASH_NEWFILE;
71211996SThomas.Whitten@Sun.COM } else {
71311996SThomas.Whitten@Sun.COM uu_free(pname);
71411996SThomas.Whitten@Sun.COM ret = MHASH_RECONCILED;
71511996SThomas.Whitten@Sun.COM }
71611996SThomas.Whitten@Sun.COM return (ret);
7172462Scasper }
7182462Scasper }
7192462Scasper }
7200Sstevel@tonic-gate
7212462Scasper /*
7222462Scasper * Compute the file hash as we can no longer avoid having to know it.
7232462Scasper * Note: from this point on "hash" contains the full, current, hash.
7242462Scasper */
7252462Scasper if (md5_hash_file(file, st.st_size, &hash[MHASH_SIZE_OLD]) != 0) {
7262462Scasper uu_free(pname);
7272462Scasper return (MHASH_FAILURE);
7282462Scasper }
7292462Scasper if (hashash) {
7302462Scasper uchar_t hash_v1[MHASH_SIZE_OLD];
7310Sstevel@tonic-gate
7322462Scasper if (metahashok ||
7332462Scasper memcmp(&hash[MHASH_SIZE_OLD], &stored_hash[MHASH_SIZE_OLD],
7342462Scasper MD5_DIGEST_LENGTH) == 0) {
7352462Scasper
7362462Scasper /*
7372462Scasper * Reconcile entry: we get here when either the
7382462Scasper * meta data hash matches or the content hash matches;
7392462Scasper * we then update the database with the complete
7402462Scasper * new hash so we can be a bit quicker next time.
7412462Scasper */
74211996SThomas.Whitten@Sun.COM (void) mhash_store_entry(hndl, pname, file, hash,
74311996SThomas.Whitten@Sun.COM APPLY_NONE, NULL);
74411996SThomas.Whitten@Sun.COM if (action == APPLY_LATE) {
74511996SThomas.Whitten@Sun.COM if (pnamep != NULL)
74611996SThomas.Whitten@Sun.COM *pnamep = pname;
74711996SThomas.Whitten@Sun.COM ret = MHASH_NEWFILE;
74811996SThomas.Whitten@Sun.COM } else {
74911996SThomas.Whitten@Sun.COM uu_free(pname);
75011996SThomas.Whitten@Sun.COM ret = MHASH_RECONCILED;
75111996SThomas.Whitten@Sun.COM }
75211996SThomas.Whitten@Sun.COM return (ret);
7530Sstevel@tonic-gate }
7540Sstevel@tonic-gate
7550Sstevel@tonic-gate /*
7562462Scasper * No match on V2 hash or file content; compare V1 hash.
7570Sstevel@tonic-gate */
7580Sstevel@tonic-gate data = uu_msprintf(MHASH_FORMAT_V1, st.st_ino, st.st_uid,
7590Sstevel@tonic-gate st.st_size, st.st_mtime);
7601061Scasper if (data == NULL) {
7611061Scasper uu_free(pname);
7622462Scasper return (MHASH_FAILURE);
7631061Scasper }
7640Sstevel@tonic-gate
7650Sstevel@tonic-gate md5_calc(hash_v1, (uchar_t *)data, strlen(data));
7660Sstevel@tonic-gate
7670Sstevel@tonic-gate uu_free(data);
7680Sstevel@tonic-gate
7691061Scasper if (memcmp(hash_v1, stored_hash, MD5_DIGEST_LENGTH) == 0) {
7702462Scasper /*
7712462Scasper * Update the new entry so we don't have to go through
7722462Scasper * all this trouble next time.
7732462Scasper */
77411996SThomas.Whitten@Sun.COM (void) mhash_store_entry(hndl, pname, file, hash,
77511996SThomas.Whitten@Sun.COM APPLY_NONE, NULL);
7761061Scasper uu_free(pname);
7772462Scasper return (MHASH_RECONCILED);
7781061Scasper }
7790Sstevel@tonic-gate }
7800Sstevel@tonic-gate
7812462Scasper if (pnamep != NULL)
7822462Scasper *pnamep = pname;
7832462Scasper else
7842462Scasper uu_free(pname);
7850Sstevel@tonic-gate
7862462Scasper if (hashbuf != NULL)
7872462Scasper (void) memcpy(hashbuf, hash, MHASH_SIZE);
7882462Scasper
7892462Scasper return (MHASH_NEWFILE);
7900Sstevel@tonic-gate }
791