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.
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.
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]
22 * Copyright (c) 2004, 2010, Oracle and/or its affiliates. All rights reserved.
23 * Copyright 2018 OmniOS Community Edition (OmniOSce) Association.
28 #include <sys/types.h>
46 #include <manifest_hash.h>
49 * Translate a file name to property name. Return an allocated string or NULL
50 * if realpath() fails. If deathrow is true, realpath() is skipped. This
51 * allows to return the property name even if the file doesn't exist.
54 mhash_filename_to_propname(const char *in
, boolean_t deathrow
)
56 char *out
, *cp
, *base
;
57 size_t len
, piece_len
;
60 out
= uu_zalloc(PATH_MAX
+ 1);
62 /* used only for service deathrow handling */
63 if (strlcpy(out
, in
, PATH_MAX
+ 1) >= (PATH_MAX
+ 1)) {
68 if (realpath(in
, out
) == NULL
) {
74 base
= getenv("PKG_INSTALL_ROOT");
77 * We copy-shift over the basedir and the leading slash, since it's
78 * not relevant to when we boot with this repository.
81 if (base
!= NULL
&& strncmp(out
, base
, strlen(base
)) == 0)
82 base_sz
= strlen(base
);
87 (void) memmove(out
, cp
, strlen(cp
) + 1);
90 if (len
> scf_limit(SCF_LIMIT_MAX_NAME_LENGTH
)) {
91 /* Use the first half and the second half. */
92 piece_len
= (scf_limit(SCF_LIMIT_MAX_NAME_LENGTH
) - 3) / 2;
94 (void) strncpy(out
+ piece_len
, "__", 2);
96 (void) memmove(out
+ piece_len
+ 2, out
+ (len
- piece_len
),
101 * Translate non-property characters to '_', first making sure that
102 * we don't begin with '_'.
108 for (cp
= out
+ 1; *cp
!= '\0'; ++cp
) {
109 if (!(isalnum(*cp
) || *cp
== '_' || *cp
== '-'))
117 mhash_retrieve_entry(scf_handle_t
*hndl
, const char *name
, uchar_t
*hash
,
118 apply_action_t
*action
)
122 scf_propertygroup_t
*pg
;
123 scf_property_t
*prop
;
130 *action
= APPLY_NONE
;
133 * In this implementation the hash for name is the opaque value of
134 * svc:/MHASH_SVC/:properties/name/MHASH_PROP
137 scope
= scf_scope_create(hndl
);
138 svc
= scf_service_create(hndl
);
139 pg
= scf_pg_create(hndl
);
140 prop
= scf_property_create(hndl
);
141 val
= scf_value_create(hndl
);
142 if (scope
== NULL
|| svc
== NULL
|| pg
== NULL
|| prop
== NULL
||
148 if (scf_handle_get_local_scope(hndl
, scope
) < 0) {
153 if (scf_scope_get_service(scope
, MHASH_SVC
, svc
) < 0) {
158 if (scf_service_get_pg(svc
, name
, pg
) != SCF_SUCCESS
) {
163 if (scf_pg_get_property(pg
, MHASH_PROP
, prop
) != SCF_SUCCESS
) {
168 if (scf_property_get_value(prop
, val
) != SCF_SUCCESS
) {
173 szret
= scf_value_get_opaque(val
, hash
, MHASH_SIZE
);
180 * Make sure that the old hash is returned with
181 * remainder of the bytes zeroed.
183 if (szret
== MHASH_SIZE_OLD
) {
184 (void) memset(hash
+ MHASH_SIZE_OLD
, 0,
185 MHASH_SIZE
- MHASH_SIZE_OLD
);
186 } else if (szret
!= MHASH_SIZE
) {
187 scf_value_destroy(val
);
193 * If caller has requested the apply_last property, read the
194 * property if it exists.
196 if (action
!= NULL
) {
199 if (scf_pg_get_property(pg
, MHASH_APPLY_PROP
, prop
) !=
202 if ((err
!= SCF_ERROR_DELETED
) &&
203 (err
!= SCF_ERROR_NOT_FOUND
)) {
208 if (scf_property_get_value(prop
, val
) != SCF_SUCCESS
) {
210 if ((err
!= SCF_ERROR_DELETED
) &&
211 (err
!= SCF_ERROR_NOT_FOUND
)) {
216 if (scf_value_get_boolean(val
, &apply_value
) != SCF_SUCCESS
) {
221 *action
= APPLY_LATE
;
225 (void) scf_value_destroy(val
);
226 scf_property_destroy(prop
);
228 scf_service_destroy(svc
);
229 scf_scope_destroy(scope
);
235 mhash_store_entry(scf_handle_t
*hndl
, const char *name
, const char *fname
,
236 uchar_t
*hash
, apply_action_t apply_late
, char **errstr
)
238 scf_scope_t
*scope
= NULL
;
239 scf_service_t
*svc
= NULL
;
240 scf_propertygroup_t
*pg
= NULL
;
241 scf_property_t
*prop
= NULL
;
242 scf_value_t
*aval
= NULL
;
243 scf_value_t
*val
= NULL
;
244 scf_value_t
*fval
= NULL
;
245 scf_transaction_t
*tx
= NULL
;
246 scf_transaction_entry_t
*ae
= NULL
;
247 scf_transaction_entry_t
*e
= NULL
;
248 scf_transaction_entry_t
*fe
= NULL
;
256 if ((scope
= scf_scope_create(hndl
)) == NULL
||
257 (svc
= scf_service_create(hndl
)) == NULL
||
258 (pg
= scf_pg_create(hndl
)) == NULL
||
259 (prop
= scf_property_create(hndl
)) == NULL
) {
261 *errstr
= gettext("Could not create scf objects");
266 if (scf_handle_get_local_scope(hndl
, scope
) != SCF_SUCCESS
) {
268 *errstr
= gettext("Could not get local scope");
273 for (i
= 0; i
< 5; ++i
) {
275 if (scf_scope_get_service(scope
, MHASH_SVC
, svc
) ==
279 if (scf_error() != SCF_ERROR_NOT_FOUND
) {
281 *errstr
= gettext("Could not get manifest hash "
287 if (scf_scope_add_service(scope
, MHASH_SVC
, svc
) ==
293 if (err
== SCF_ERROR_EXISTS
)
296 else if (err
== SCF_ERROR_PERMISSION_DENIED
) {
298 *errstr
= gettext("Could not store file hash: "
299 "permission denied.\n");
305 *errstr
= gettext("Could not add manifest hash "
313 *errstr
= gettext("Could not store file hash: "
314 "service addition contention.\n");
319 for (i
= 0; i
< 5; ++i
) {
320 if (scf_service_get_pg(svc
, name
, pg
) == SCF_SUCCESS
)
323 if (scf_error() != SCF_ERROR_NOT_FOUND
) {
325 *errstr
= gettext("Could not get service's "
331 if (scf_service_add_pg(svc
, name
, MHASH_PG_TYPE
,
332 MHASH_PG_FLAGS
, pg
) == SCF_SUCCESS
)
337 if (err
== SCF_ERROR_EXISTS
)
340 else if (err
== SCF_ERROR_PERMISSION_DENIED
) {
342 *errstr
= gettext("Could not store file hash: "
343 "permission denied.\n");
349 *errstr
= gettext("Could not store file hash");
355 *errstr
= gettext("Could not store file hash: "
356 "property group addition contention.\n");
361 if ((e
= scf_entry_create(hndl
)) == NULL
||
362 (val
= scf_value_create(hndl
)) == NULL
||
363 (fe
= scf_entry_create(hndl
)) == NULL
||
364 (fval
= scf_value_create(hndl
)) == NULL
||
365 (ae
= scf_entry_create(hndl
)) == NULL
||
366 (aval
= scf_value_create(hndl
)) == NULL
) {
368 *errstr
= gettext("Could not store file hash: "
369 "permission denied.\n");
375 * Remove any PKG_INSTALL_ROOT from the manifest filename so that it
376 * points to the correct location following installation.
378 base
= getenv("PKG_INSTALL_ROOT");
379 if (base
!= NULL
&& strncmp(fname
, base
, strlen(base
)) == 0)
380 base_sz
= strlen(base
);
382 ret
= scf_value_set_opaque(val
, hash
, MHASH_SIZE
);
383 assert(ret
== SCF_SUCCESS
);
384 ret
= scf_value_set_astring(fval
, fname
+ base_sz
);
385 assert(ret
== SCF_SUCCESS
);
386 if (apply_late
== APPLY_LATE
) {
387 scf_value_set_boolean(aval
, 1);
390 tx
= scf_transaction_create(hndl
);
393 *errstr
= gettext("Could not create transaction");
399 if (scf_pg_update(pg
) == -1) {
401 *errstr
= gettext("Could not update hash "
406 if (scf_transaction_start(tx
, pg
) != SCF_SUCCESS
) {
407 if (scf_error() != SCF_ERROR_PERMISSION_DENIED
) {
409 *errstr
= gettext("Could not start "
410 "hash transaction.\n");
416 *errstr
= gettext("Could not store file hash: "
417 "permission denied.\n");
420 scf_transaction_destroy(tx
);
421 (void) scf_entry_destroy(e
);
425 if (scf_transaction_property_new(tx
, e
, MHASH_PROP
,
426 SCF_TYPE_OPAQUE
) != SCF_SUCCESS
&&
427 scf_transaction_property_change_type(tx
, e
, MHASH_PROP
,
428 SCF_TYPE_OPAQUE
) != SCF_SUCCESS
) {
430 *errstr
= gettext("Could not modify hash "
436 ret
= scf_entry_add_value(e
, val
);
437 assert(ret
== SCF_SUCCESS
);
439 if (scf_transaction_property_new(tx
, fe
, MHASH_FILE_PROP
,
440 SCF_TYPE_ASTRING
) != SCF_SUCCESS
&&
441 scf_transaction_property_change_type(tx
, fe
,
442 MHASH_FILE_PROP
, SCF_TYPE_ASTRING
) != SCF_SUCCESS
) {
444 *errstr
= gettext("Could not modify file "
450 ret
= scf_entry_add_value(fe
, fval
);
451 assert(ret
== SCF_SUCCESS
);
453 switch (apply_late
) {
455 if (scf_transaction_property_delete(tx
, ae
,
456 MHASH_APPLY_PROP
) != 0) {
458 if ((err
!= SCF_ERROR_DELETED
) &&
459 (err
!= SCF_ERROR_NOT_FOUND
)) {
460 if (errstr
!= NULL
) {
461 *errstr
= gettext("Could not "
471 if ((scf_transaction_property_new(tx
, ae
,
473 SCF_TYPE_BOOLEAN
) != SCF_SUCCESS
) &&
474 (scf_transaction_property_change_type(tx
, ae
,
475 MHASH_APPLY_PROP
, SCF_TYPE_BOOLEAN
) !=
477 if (errstr
!= NULL
) {
478 *errstr
= gettext("Could not modify "
479 "apply_late property");
485 ret
= scf_entry_add_value(ae
, aval
);
486 assert(ret
== SCF_SUCCESS
);
492 ret
= scf_transaction_commit(tx
);
495 scf_transaction_reset(tx
);
499 if (scf_error() != SCF_ERROR_PERMISSION_DENIED
) {
501 *errstr
= gettext("Could not store file hash: "
502 "permission denied.\n");
508 *errstr
= gettext("Could not commit transaction");
512 scf_transaction_destroy(tx
);
513 (void) scf_entry_destroy(e
);
514 (void) scf_entry_destroy(fe
);
515 (void) scf_entry_destroy(ae
);
518 (void) scf_value_destroy(val
);
519 (void) scf_value_destroy(fval
);
520 (void) scf_value_destroy(aval
);
521 scf_property_destroy(prop
);
523 scf_service_destroy(svc
);
524 scf_scope_destroy(scope
);
530 * Generate the md5 hash of a file; manifest files are smallish
531 * so we can read them in one gulp.
534 md5_hash_file(const char *file
, off64_t sz
, uchar_t
*hash
)
541 fd
= open(file
, O_RDONLY
);
551 res
= read(fd
, buf
, (size_t)sz
);
557 md5_calc(hash
, (uchar_t
*)buf
, (unsigned int) sz
);
567 * int mhash_test_file(scf_handle_t *, const char *, uint_t, char **, uchar_t *)
568 * Test the given filename against the hashed metadata in the repository.
569 * The behaviours for import and apply are slightly different. For imports,
570 * if the hash value is absent or different, then the import operation
571 * continues. For profile application, the operation continues only if the
572 * hash value for the file is absent.
574 * We keep two hashes: one which can be quickly test: the metadata hash,
575 * and one which is more expensive to test: the file contents hash.
577 * If either hash matches, the file does not need to be re-read.
578 * If only one of the hashes matches, a side effect of this function
579 * is to store the newly computed hash.
580 * If neither hash matches, the hash computed for the new file is returned
584 * MHASH_NEWFILE - the file no longer matches the hash or no hash existed
585 * ONLY in this case we return the new file's hash.
586 * MHASH_FAILURE - an internal error occurred, or the file was not found.
587 * MHASH_RECONCILED- based on the metadata/file hash, the file does
588 * not need to be re-read; if necessary,
589 * the hash was upgraded or reconciled.
591 * NOTE: no hash is returned UNLESS MHASH_NEWFILE is returned.
594 mhash_test_file(scf_handle_t
*hndl
, const char *file
, uint_t is_profile
,
595 char **pnamep
, uchar_t
*hashbuf
)
597 apply_action_t action
;
602 uchar_t stored_hash
[MHASH_SIZE
];
603 uchar_t hash
[MHASH_SIZE
];
613 * In the case where we are doing automated imports, we reduce the UID,
614 * the GID, the size, and the mtime into a string (to eliminate
615 * endianness) which we then make opaque as a single MD5 digest.
617 * The previous hash was composed of the inode number, the UID, the file
618 * size, and the mtime. This formulation was found to be insufficiently
619 * portable for use in highly replicated deployments. The current
620 * algorithm will allow matches of this "v1" hash, but always returns
621 * the effective "v2" hash, such that updates result in the more
622 * portable hash being used.
624 * An unwanted side effect of a hash based solely on the file
625 * meta data is the fact that we pay no attention to the contents
626 * which may remain the same despite meta data changes. This happens
627 * with (live) upgrades. We extend the V2 hash with an additional
628 * digest of the file contents and the code retrieving the hash
629 * from the repository zero fills the remainder so we can detect
632 * If the the V2 digest matches, we check for the presence of
633 * the contents digest and compute and store it if missing.
635 * If the V2 digest doesn't match but we also have a non-zero
636 * file hash, we match the file content digest. If it matches,
637 * we compute and store the new complete hash so that later
638 * checks will find the meta data digest correct.
640 * If the above matches fail and the V1 hash doesn't match either,
641 * we consider the test to have failed, implying that some aspect
642 * of the manifest has changed.
645 cp
= getenv("SVCCFG_CHECKHASH");
646 do_hash
= (cp
!= NULL
&& *cp
!= '\0');
648 return (MHASH_NEWFILE
);
651 pname
= mhash_filename_to_propname(file
, B_FALSE
);
653 return (MHASH_FAILURE
);
655 hashash
= mhash_retrieve_entry(hndl
, pname
, stored_hash
, &action
) == 0;
656 if (is_profile
== 0) {
657 /* Actions other than APPLY_NONE are restricted to profiles. */
658 assert(action
== APPLY_NONE
);
662 * As a general rule, we do not reread a profile. The exception to
663 * this rule is when we are running as part of the manifest import
664 * service and the apply_late property is set to true.
666 if (hashash
&& is_profile
) {
667 cp
= getenv("SMF_FMRI");
669 (strcmp(cp
, SCF_INSTANCE_MI
) != 0) ||
670 (action
!= APPLY_LATE
)) {
672 return (MHASH_RECONCILED
);
677 * No hash and not interested in one, then don't bother computing it.
678 * We also skip returning the property name in that case.
680 if (!hashash
&& hashbuf
== NULL
) {
682 return (MHASH_NEWFILE
);
686 ret
= stat64(file
, &st
);
687 } while (ret
< 0 && errno
== EINTR
);
690 return (MHASH_FAILURE
);
693 data
= uu_msprintf(MHASH_FORMAT_V2
, st
.st_uid
, st
.st_gid
,
694 st
.st_size
, st
.st_mtime
);
697 return (MHASH_FAILURE
);
700 (void) memset(hash
, 0, MHASH_SIZE
);
701 md5_calc(hash
, (uchar_t
*)data
, strlen(data
));
706 * Verify the meta data hash.
708 if (hashash
&& memcmp(hash
, stored_hash
, MD5_DIGEST_LENGTH
) == 0) {
713 * The metadata hash matches; now we see if there was a
714 * content hash; if not, we will continue on and compute and
715 * store the updated hash.
716 * If there was no content hash, mhash_retrieve_entry()
717 * will have zero filled it.
719 for (i
= 0; i
< MD5_DIGEST_LENGTH
; i
++) {
720 if (stored_hash
[MD5_DIGEST_LENGTH
+i
] != 0) {
721 if (action
== APPLY_LATE
) {
727 ret
= MHASH_RECONCILED
;
735 * Compute the file hash as we can no longer avoid having to know it.
736 * Note: from this point on "hash" contains the full, current, hash.
738 if (md5_hash_file(file
, st
.st_size
, &hash
[MHASH_SIZE_OLD
]) != 0) {
740 return (MHASH_FAILURE
);
743 uchar_t hash_v1
[MHASH_SIZE_OLD
];
746 memcmp(&hash
[MHASH_SIZE_OLD
], &stored_hash
[MHASH_SIZE_OLD
],
747 MD5_DIGEST_LENGTH
) == 0) {
750 * Reconcile entry: we get here when either the
751 * meta data hash matches or the content hash matches;
752 * we then update the database with the complete
753 * new hash so we can be a bit quicker next time.
755 (void) mhash_store_entry(hndl
, pname
, file
, hash
,
757 if (action
== APPLY_LATE
) {
763 ret
= MHASH_RECONCILED
;
769 * No match on V2 hash or file content; compare V1 hash.
771 data
= uu_msprintf(MHASH_FORMAT_V1
, st
.st_ino
, st
.st_uid
,
772 st
.st_size
, st
.st_mtime
);
775 return (MHASH_FAILURE
);
778 md5_calc(hash_v1
, (uchar_t
*)data
, strlen(data
));
782 if (memcmp(hash_v1
, stored_hash
, MD5_DIGEST_LENGTH
) == 0) {
784 * Update the new entry so we don't have to go through
785 * all this trouble next time.
787 (void) mhash_store_entry(hndl
, pname
, file
, hash
,
790 return (MHASH_RECONCILED
);
800 (void) memcpy(hashbuf
, hash
, MHASH_SIZE
);
802 return (MHASH_NEWFILE
);