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 (c) 2004, 2010, Oracle and/or its affiliates. All rights reserved. 23 */ 24 25 26 #include <sys/stat.h> 27 #include <sys/types.h> 28 29 #include <assert.h> 30 #include <ctype.h> 31 #include <errno.h> 32 #include <fcntl.h> 33 #include <libintl.h> 34 #include <libscf.h> 35 #include <libuutil.h> 36 #include <limits.h> 37 #include <md5.h> 38 #include <pthread.h> 39 #include <stdio.h> 40 #include <stdlib.h> 41 #include <string.h> 42 #include <strings.h> 43 #include <unistd.h> 44 45 #include <manifest_hash.h> 46 47 /* 48 * Translate a file name to property name. Return an allocated string or NULL 49 * if realpath() fails. If deathrow is true, realpath() is skipped. This 50 * allows to return the property name even if the file doesn't exist. 51 */ 52 char * 53 mhash_filename_to_propname(const char *in, boolean_t deathrow) 54 { 55 char *out, *cp, *base; 56 size_t len, piece_len; 57 size_t base_sz = 0; 58 59 out = uu_zalloc(PATH_MAX + 1); 60 if (deathrow) { 61 /* used only for service deathrow handling */ 62 if (strlcpy(out, in, PATH_MAX + 1) >= (PATH_MAX + 1)) { 63 uu_free(out); 64 return (NULL); 65 } 66 } else { 67 if (realpath(in, out) == NULL) { 68 uu_free(out); 69 return (NULL); 70 } 71 } 72 73 base = getenv("PKG_INSTALL_ROOT"); 74 75 /* 76 * We copy-shift over the basedir and the leading slash, since it's 77 * not relevant to when we boot with this repository. 78 */ 79 80 if (base != NULL && strncmp(out, base, strlen(base)) == 0) 81 base_sz = strlen(base); 82 83 cp = out + base_sz; 84 if (*cp == '/') 85 cp++; 86 (void) memmove(out, cp, strlen(cp) + 1); 87 88 len = strlen(out); 89 if (len > scf_limit(SCF_LIMIT_MAX_NAME_LENGTH)) { 90 /* Use the first half and the second half. */ 91 piece_len = (scf_limit(SCF_LIMIT_MAX_NAME_LENGTH) - 3) / 2; 92 93 (void) strncpy(out + piece_len, "__", 2); 94 95 (void) memmove(out + piece_len + 2, out + (len - piece_len), 96 piece_len + 1); 97 } 98 99 /* 100 * Translate non-property characters to '_', first making sure that 101 * we don't begin with '_'. 102 */ 103 104 if (!isalpha(*out)) 105 *out = 'A'; 106 107 for (cp = out + 1; *cp != '\0'; ++cp) { 108 if (!(isalnum(*cp) || *cp == '_' || *cp == '-')) 109 *cp = '_'; 110 } 111 112 return (out); 113 } 114 115 int 116 mhash_retrieve_entry(scf_handle_t *hndl, const char *name, uchar_t *hash, 117 apply_action_t *action) 118 { 119 scf_scope_t *scope; 120 scf_service_t *svc; 121 scf_propertygroup_t *pg; 122 scf_property_t *prop; 123 scf_value_t *val; 124 scf_error_t err; 125 ssize_t szret; 126 int result = 0; 127 128 if (action) 129 *action = APPLY_NONE; 130 131 /* 132 * In this implementation the hash for name is the opaque value of 133 * svc:/MHASH_SVC/:properties/name/MHASH_PROP 134 */ 135 136 if ((scope = scf_scope_create(hndl)) == NULL || 137 (svc = scf_service_create(hndl)) == NULL || 138 (pg = scf_pg_create(hndl)) == NULL || 139 (prop = scf_property_create(hndl)) == NULL || 140 (val = scf_value_create(hndl)) == NULL) { 141 result = -1; 142 goto out; 143 } 144 145 if (scf_handle_get_local_scope(hndl, scope) < 0) { 146 result = -1; 147 goto out; 148 } 149 150 if (scf_scope_get_service(scope, MHASH_SVC, svc) < 0) { 151 result = -1; 152 goto out; 153 } 154 155 if (scf_service_get_pg(svc, name, pg) != SCF_SUCCESS) { 156 result = -1; 157 goto out; 158 } 159 160 if (scf_pg_get_property(pg, MHASH_PROP, prop) != SCF_SUCCESS) { 161 result = -1; 162 goto out; 163 } 164 165 if (scf_property_get_value(prop, val) != SCF_SUCCESS) { 166 result = -1; 167 goto out; 168 } 169 170 szret = scf_value_get_opaque(val, hash, MHASH_SIZE); 171 if (szret < 0) { 172 result = -1; 173 goto out; 174 } 175 176 /* 177 * Make sure that the old hash is returned with 178 * remainder of the bytes zeroed. 179 */ 180 if (szret == MHASH_SIZE_OLD) { 181 (void) memset(hash + MHASH_SIZE_OLD, 0, 182 MHASH_SIZE - MHASH_SIZE_OLD); 183 } else if (szret != MHASH_SIZE) { 184 scf_value_destroy(val); 185 result = -1; 186 goto out; 187 } 188 189 /* 190 * If caller has requested the apply_last property, read the 191 * property if it exists. 192 */ 193 if (action != NULL) { 194 uint8_t apply_value; 195 196 if (scf_pg_get_property(pg, MHASH_APPLY_PROP, prop) != 197 SCF_SUCCESS) { 198 err = scf_error(); 199 if ((err != SCF_ERROR_DELETED) && 200 (err != SCF_ERROR_NOT_FOUND)) { 201 result = -1; 202 } 203 goto out; 204 } 205 if (scf_property_get_value(prop, val) != SCF_SUCCESS) { 206 err = scf_error(); 207 if ((err != SCF_ERROR_DELETED) && 208 (err != SCF_ERROR_NOT_FOUND)) { 209 result = -1; 210 } 211 goto out; 212 } 213 if (scf_value_get_boolean(val, &apply_value) != SCF_SUCCESS) { 214 result = -1; 215 goto out; 216 } 217 if (apply_value) 218 *action = APPLY_LATE; 219 } 220 221 out: 222 (void) scf_value_destroy(val); 223 scf_property_destroy(prop); 224 scf_pg_destroy(pg); 225 scf_service_destroy(svc); 226 scf_scope_destroy(scope); 227 228 return (result); 229 } 230 231 int 232 mhash_store_entry(scf_handle_t *hndl, const char *name, const char *fname, 233 uchar_t *hash, apply_action_t apply_late, char **errstr) 234 { 235 scf_scope_t *scope = NULL; 236 scf_service_t *svc = NULL; 237 scf_propertygroup_t *pg = NULL; 238 scf_property_t *prop = NULL; 239 scf_value_t *aval = NULL; 240 scf_value_t *val = NULL; 241 scf_value_t *fval = NULL; 242 scf_transaction_t *tx = NULL; 243 scf_transaction_entry_t *ae = NULL; 244 scf_transaction_entry_t *e = NULL; 245 scf_transaction_entry_t *fe = NULL; 246 scf_error_t err; 247 int ret, result = 0; 248 249 int i; 250 251 if ((scope = scf_scope_create(hndl)) == NULL || 252 (svc = scf_service_create(hndl)) == NULL || 253 (pg = scf_pg_create(hndl)) == NULL || 254 (prop = scf_property_create(hndl)) == NULL) { 255 if (errstr != NULL) 256 *errstr = gettext("Could not create scf objects"); 257 result = -1; 258 goto out; 259 } 260 261 if (scf_handle_get_local_scope(hndl, scope) != SCF_SUCCESS) { 262 if (errstr != NULL) 263 *errstr = gettext("Could not get local scope"); 264 result = -1; 265 goto out; 266 } 267 268 for (i = 0; i < 5; ++i) { 269 270 if (scf_scope_get_service(scope, MHASH_SVC, svc) == 271 SCF_SUCCESS) 272 break; 273 274 if (scf_error() != SCF_ERROR_NOT_FOUND) { 275 if (errstr != NULL) 276 *errstr = gettext("Could not get manifest hash " 277 "service"); 278 result = -1; 279 goto out; 280 } 281 282 if (scf_scope_add_service(scope, MHASH_SVC, svc) == 283 SCF_SUCCESS) 284 break; 285 286 err = scf_error(); 287 288 if (err == SCF_ERROR_EXISTS) 289 /* Try again. */ 290 continue; 291 else if (err == SCF_ERROR_PERMISSION_DENIED) { 292 if (errstr != NULL) 293 *errstr = gettext("Could not store file hash: " 294 "permission denied.\n"); 295 result = -1; 296 goto out; 297 } 298 299 if (errstr != NULL) 300 *errstr = gettext("Could not add manifest hash " 301 "service"); 302 result = -1; 303 goto out; 304 } 305 306 if (i == 5) { 307 if (errstr != NULL) 308 *errstr = gettext("Could not store file hash: " 309 "service addition contention.\n"); 310 result = -1; 311 goto out; 312 } 313 314 for (i = 0; i < 5; ++i) { 315 if (scf_service_get_pg(svc, name, pg) == SCF_SUCCESS) 316 break; 317 318 if (scf_error() != SCF_ERROR_NOT_FOUND) { 319 if (errstr != NULL) 320 *errstr = gettext("Could not get service's " 321 "hash record)"); 322 result = -1; 323 goto out; 324 } 325 326 if (scf_service_add_pg(svc, name, MHASH_PG_TYPE, 327 MHASH_PG_FLAGS, pg) == SCF_SUCCESS) 328 break; 329 330 err = scf_error(); 331 332 if (err == SCF_ERROR_EXISTS) 333 /* Try again. */ 334 continue; 335 else if (err == SCF_ERROR_PERMISSION_DENIED) { 336 if (errstr != NULL) 337 *errstr = gettext("Could not store file hash: " 338 "permission denied.\n"); 339 result = -1; 340 goto out; 341 } 342 343 if (errstr != NULL) 344 *errstr = gettext("Could not store file hash"); 345 result = -1; 346 goto out; 347 } 348 if (i == 5) { 349 if (errstr != NULL) 350 *errstr = gettext("Could not store file hash: " 351 "property group addition contention.\n"); 352 result = -1; 353 goto out; 354 } 355 356 if ((e = scf_entry_create(hndl)) == NULL || 357 (val = scf_value_create(hndl)) == NULL || 358 (fe = scf_entry_create(hndl)) == NULL || 359 (fval = scf_value_create(hndl)) == NULL || 360 (ae = scf_entry_create(hndl)) == NULL || 361 (aval = scf_value_create(hndl)) == NULL) { 362 if (errstr != NULL) 363 *errstr = gettext("Could not store file hash: " 364 "permission denied.\n"); 365 result = -1; 366 goto out; 367 } 368 369 ret = scf_value_set_opaque(val, hash, MHASH_SIZE); 370 assert(ret == SCF_SUCCESS); 371 ret = scf_value_set_astring(fval, fname); 372 assert(ret == SCF_SUCCESS); 373 if (apply_late == APPLY_LATE) { 374 scf_value_set_boolean(aval, 1); 375 } 376 377 tx = scf_transaction_create(hndl); 378 if (tx == NULL) { 379 if (errstr != NULL) 380 *errstr = gettext("Could not create transaction"); 381 result = -1; 382 goto out; 383 } 384 385 do { 386 if (scf_pg_update(pg) == -1) { 387 if (errstr != NULL) 388 *errstr = gettext("Could not update hash " 389 "entry"); 390 result = -1; 391 goto out; 392 } 393 if (scf_transaction_start(tx, pg) != SCF_SUCCESS) { 394 if (scf_error() != SCF_ERROR_PERMISSION_DENIED) { 395 if (errstr != NULL) 396 *errstr = gettext("Could not start " 397 "hash transaction.\n"); 398 result = -1; 399 goto out; 400 } 401 402 if (errstr != NULL) 403 *errstr = gettext("Could not store file hash: " 404 "permission denied.\n"); 405 result = -1; 406 407 scf_transaction_destroy(tx); 408 (void) scf_entry_destroy(e); 409 goto out; 410 } 411 412 if (scf_transaction_property_new(tx, e, MHASH_PROP, 413 SCF_TYPE_OPAQUE) != SCF_SUCCESS && 414 scf_transaction_property_change_type(tx, e, MHASH_PROP, 415 SCF_TYPE_OPAQUE) != SCF_SUCCESS) { 416 if (errstr != NULL) 417 *errstr = gettext("Could not modify hash " 418 "entry"); 419 result = -1; 420 goto out; 421 } 422 423 ret = scf_entry_add_value(e, val); 424 assert(ret == SCF_SUCCESS); 425 426 if (scf_transaction_property_new(tx, fe, MHASH_FILE_PROP, 427 SCF_TYPE_ASTRING) != SCF_SUCCESS && 428 scf_transaction_property_change_type(tx, fe, 429 MHASH_FILE_PROP, SCF_TYPE_ASTRING) != SCF_SUCCESS) { 430 if (errstr != NULL) 431 *errstr = gettext("Could not modify file " 432 "entry"); 433 result = -1; 434 goto out; 435 } 436 437 ret = scf_entry_add_value(fe, fval); 438 assert(ret == SCF_SUCCESS); 439 440 switch (apply_late) { 441 case APPLY_NONE: 442 if (scf_transaction_property_delete(tx, ae, 443 MHASH_APPLY_PROP) != 0) { 444 err = scf_error(); 445 if ((err != SCF_ERROR_DELETED) && 446 (err != SCF_ERROR_NOT_FOUND)) { 447 if (errstr != NULL) { 448 *errstr = gettext("Could not " 449 "delete apply_late " 450 "property"); 451 } 452 result = -1; 453 goto out; 454 } 455 } 456 break; 457 case APPLY_LATE: 458 if ((scf_transaction_property_new(tx, ae, 459 MHASH_APPLY_PROP, 460 SCF_TYPE_BOOLEAN) != SCF_SUCCESS) && 461 (scf_transaction_property_change_type(tx, ae, 462 MHASH_APPLY_PROP, SCF_TYPE_BOOLEAN) != 463 SCF_SUCCESS)) { 464 if (errstr != NULL) { 465 *errstr = gettext("Could not modify " 466 "apply_late property"); 467 } 468 result = -1; 469 goto out; 470 } 471 472 ret = scf_entry_add_value(ae, aval); 473 assert(ret == SCF_SUCCESS); 474 break; 475 default: 476 abort(); 477 }; 478 479 ret = scf_transaction_commit(tx); 480 481 if (ret == 0) 482 scf_transaction_reset(tx); 483 } while (ret == 0); 484 485 if (ret < 0) { 486 if (scf_error() != SCF_ERROR_PERMISSION_DENIED) { 487 if (errstr != NULL) 488 *errstr = gettext("Could not store file hash: " 489 "permission denied.\n"); 490 result = -1; 491 goto out; 492 } 493 494 if (errstr != NULL) 495 *errstr = gettext("Could not commit transaction"); 496 result = -1; 497 } 498 499 scf_transaction_destroy(tx); 500 (void) scf_entry_destroy(e); 501 (void) scf_entry_destroy(fe); 502 (void) scf_entry_destroy(ae); 503 504 out: 505 (void) scf_value_destroy(val); 506 (void) scf_value_destroy(fval); 507 (void) scf_value_destroy(aval); 508 scf_property_destroy(prop); 509 scf_pg_destroy(pg); 510 scf_service_destroy(svc); 511 scf_scope_destroy(scope); 512 513 return (result); 514 } 515 516 /* 517 * Generate the md5 hash of a file; manifest files are smallish 518 * so we can read them in one gulp. 519 */ 520 static int 521 md5_hash_file(const char *file, off64_t sz, uchar_t *hash) 522 { 523 char *buf; 524 int fd; 525 ssize_t res; 526 int ret; 527 528 fd = open(file, O_RDONLY); 529 if (fd < 0) 530 return (-1); 531 532 buf = malloc(sz); 533 if (buf == NULL) { 534 (void) close(fd); 535 return (-1); 536 } 537 538 res = read(fd, buf, (size_t)sz); 539 540 (void) close(fd); 541 542 if (res == sz) { 543 ret = 0; 544 md5_calc(hash, (uchar_t *)buf, (unsigned int) sz); 545 } else { 546 ret = -1; 547 } 548 549 free(buf); 550 return (ret); 551 } 552 553 /* 554 * int mhash_test_file(scf_handle_t *, const char *, uint_t, char **, uchar_t *) 555 * Test the given filename against the hashed metadata in the repository. 556 * The behaviours for import and apply are slightly different. For imports, 557 * if the hash value is absent or different, then the import operation 558 * continues. For profile application, the operation continues only if the 559 * hash value for the file is absent. 560 * 561 * We keep two hashes: one which can be quickly test: the metadata hash, 562 * and one which is more expensive to test: the file contents hash. 563 * 564 * If either hash matches, the file does not need to be re-read. 565 * If only one of the hashes matches, a side effect of this function 566 * is to store the newly computed hash. 567 * If neither hash matches, the hash computed for the new file is returned 568 * and not stored. 569 * 570 * Return values: 571 * MHASH_NEWFILE - the file no longer matches the hash or no hash existed 572 * ONLY in this case we return the new file's hash. 573 * MHASH_FAILURE - an internal error occurred, or the file was not found. 574 * MHASH_RECONCILED- based on the metadata/file hash, the file does 575 * not need to be re-read; if necessary, 576 * the hash was upgraded or reconciled. 577 * 578 * NOTE: no hash is returned UNLESS MHASH_NEWFILE is returned. 579 */ 580 int 581 mhash_test_file(scf_handle_t *hndl, const char *file, uint_t is_profile, 582 char **pnamep, uchar_t *hashbuf) 583 { 584 apply_action_t action; 585 boolean_t do_hash; 586 struct stat64 st; 587 char *cp; 588 char *data; 589 uchar_t stored_hash[MHASH_SIZE]; 590 uchar_t hash[MHASH_SIZE]; 591 char *pname; 592 int ret; 593 int hashash; 594 int metahashok = 0; 595 596 if (pnamep) 597 *pnamep = NULL; 598 599 /* 600 * In the case where we are doing automated imports, we reduce the UID, 601 * the GID, the size, and the mtime into a string (to eliminate 602 * endianness) which we then make opaque as a single MD5 digest. 603 * 604 * The previous hash was composed of the inode number, the UID, the file 605 * size, and the mtime. This formulation was found to be insufficiently 606 * portable for use in highly replicated deployments. The current 607 * algorithm will allow matches of this "v1" hash, but always returns 608 * the effective "v2" hash, such that updates result in the more 609 * portable hash being used. 610 * 611 * An unwanted side effect of a hash based solely on the file 612 * meta data is the fact that we pay no attention to the contents 613 * which may remain the same despite meta data changes. This happens 614 * with (live) upgrades. We extend the V2 hash with an additional 615 * digest of the file contents and the code retrieving the hash 616 * from the repository zero fills the remainder so we can detect 617 * it is missing. 618 * 619 * If the the V2 digest matches, we check for the presence of 620 * the contents digest and compute and store it if missing. 621 * 622 * If the V2 digest doesn't match but we also have a non-zero 623 * file hash, we match the file content digest. If it matches, 624 * we compute and store the new complete hash so that later 625 * checks will find the meta data digest correct. 626 * 627 * If the above matches fail and the V1 hash doesn't match either, 628 * we consider the test to have failed, implying that some aspect 629 * of the manifest has changed. 630 */ 631 632 cp = getenv("SVCCFG_CHECKHASH"); 633 do_hash = (cp != NULL && *cp != '\0'); 634 if (!do_hash) { 635 return (MHASH_NEWFILE); 636 } 637 638 pname = mhash_filename_to_propname(file, B_FALSE); 639 if (pname == NULL) 640 return (MHASH_FAILURE); 641 642 hashash = mhash_retrieve_entry(hndl, pname, stored_hash, &action) == 0; 643 if (is_profile == 0) { 644 /* Actions other than APPLY_NONE are restricted to profiles. */ 645 assert(action == APPLY_NONE); 646 } 647 648 /* 649 * As a general rule, we do not reread a profile. The exception to 650 * this rule is when we are running as part of the manifest import 651 * service and the apply_late property is set to true. 652 */ 653 if (hashash && is_profile) { 654 cp = getenv("SMF_FMRI"); 655 if ((cp == NULL) || 656 (strcmp(cp, SCF_INSTANCE_MI) != 0) || 657 (action != APPLY_LATE)) { 658 uu_free(pname); 659 return (MHASH_RECONCILED); 660 } 661 } 662 663 /* 664 * No hash and not interested in one, then don't bother computing it. 665 * We also skip returning the property name in that case. 666 */ 667 if (!hashash && hashbuf == NULL) { 668 uu_free(pname); 669 return (MHASH_NEWFILE); 670 } 671 672 do { 673 ret = stat64(file, &st); 674 } while (ret < 0 && errno == EINTR); 675 if (ret < 0) { 676 uu_free(pname); 677 return (MHASH_FAILURE); 678 } 679 680 data = uu_msprintf(MHASH_FORMAT_V2, st.st_uid, st.st_gid, 681 st.st_size, st.st_mtime); 682 if (data == NULL) { 683 uu_free(pname); 684 return (MHASH_FAILURE); 685 } 686 687 (void) memset(hash, 0, MHASH_SIZE); 688 md5_calc(hash, (uchar_t *)data, strlen(data)); 689 690 uu_free(data); 691 692 /* 693 * Verify the meta data hash. 694 */ 695 if (hashash && memcmp(hash, stored_hash, MD5_DIGEST_LENGTH) == 0) { 696 int i; 697 698 metahashok = 1; 699 /* 700 * The metadata hash matches; now we see if there was a 701 * content hash; if not, we will continue on and compute and 702 * store the updated hash. 703 * If there was no content hash, mhash_retrieve_entry() 704 * will have zero filled it. 705 */ 706 for (i = 0; i < MD5_DIGEST_LENGTH; i++) { 707 if (stored_hash[MD5_DIGEST_LENGTH+i] != 0) { 708 if (action == APPLY_LATE) { 709 if (pnamep != NULL) 710 *pnamep = pname; 711 ret = MHASH_NEWFILE; 712 } else { 713 uu_free(pname); 714 ret = MHASH_RECONCILED; 715 } 716 return (ret); 717 } 718 } 719 } 720 721 /* 722 * Compute the file hash as we can no longer avoid having to know it. 723 * Note: from this point on "hash" contains the full, current, hash. 724 */ 725 if (md5_hash_file(file, st.st_size, &hash[MHASH_SIZE_OLD]) != 0) { 726 uu_free(pname); 727 return (MHASH_FAILURE); 728 } 729 if (hashash) { 730 uchar_t hash_v1[MHASH_SIZE_OLD]; 731 732 if (metahashok || 733 memcmp(&hash[MHASH_SIZE_OLD], &stored_hash[MHASH_SIZE_OLD], 734 MD5_DIGEST_LENGTH) == 0) { 735 736 /* 737 * Reconcile entry: we get here when either the 738 * meta data hash matches or the content hash matches; 739 * we then update the database with the complete 740 * new hash so we can be a bit quicker next time. 741 */ 742 (void) mhash_store_entry(hndl, pname, file, hash, 743 APPLY_NONE, NULL); 744 if (action == APPLY_LATE) { 745 if (pnamep != NULL) 746 *pnamep = pname; 747 ret = MHASH_NEWFILE; 748 } else { 749 uu_free(pname); 750 ret = MHASH_RECONCILED; 751 } 752 return (ret); 753 } 754 755 /* 756 * No match on V2 hash or file content; compare V1 hash. 757 */ 758 data = uu_msprintf(MHASH_FORMAT_V1, st.st_ino, st.st_uid, 759 st.st_size, st.st_mtime); 760 if (data == NULL) { 761 uu_free(pname); 762 return (MHASH_FAILURE); 763 } 764 765 md5_calc(hash_v1, (uchar_t *)data, strlen(data)); 766 767 uu_free(data); 768 769 if (memcmp(hash_v1, stored_hash, MD5_DIGEST_LENGTH) == 0) { 770 /* 771 * Update the new entry so we don't have to go through 772 * all this trouble next time. 773 */ 774 (void) mhash_store_entry(hndl, pname, file, hash, 775 APPLY_NONE, NULL); 776 uu_free(pname); 777 return (MHASH_RECONCILED); 778 } 779 } 780 781 if (pnamep != NULL) 782 *pnamep = pname; 783 else 784 uu_free(pname); 785 786 if (hashbuf != NULL) 787 (void) memcpy(hashbuf, hash, MHASH_SIZE); 788 789 return (MHASH_NEWFILE); 790 } 791