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