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 /* 23 * Copyright (c) 2017 Peter Tribble. 24 */ 25 26 /* 27 * Copyright (c) 1989, 2010, Oracle and/or its affiliates. All rights reserved. 28 */ 29 30 /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ 31 /* All Rights Reserved */ 32 33 34 #include <stdio.h> 35 #include <fcntl.h> 36 #include <sys/types.h> 37 #include <sys/param.h> 38 #include <sys/sysmacros.h> 39 #include <string.h> 40 #include <strings.h> 41 #include <sys/wait.h> 42 #include <sys/stat.h> 43 #include <sys/mman.h> 44 #include <sys/statvfs.h> 45 #include <signal.h> 46 #include <limits.h> 47 #include <errno.h> 48 #include <fcntl.h> 49 #include <stdlib.h> 50 #include <unistd.h> 51 #include <time.h> 52 #include <errno.h> 53 #include <pkglocs.h> 54 #include <locale.h> 55 #include <libintl.h> 56 #include <pkglib.h> 57 #include "libinst.h" 58 #include "libadm.h" 59 60 #define LOCKFILE ".pkg.lock.client" 61 #define LOCKFILESERV ".pkg.lock" 62 63 #define LOCKWAIT 10 /* seconds between retries */ 64 #define LOCKRETRY 20 /* number of retries for a DB lock */ 65 66 #define ERR_COMMIT "WARNING: unable to commit contents database update" 67 #define ERR_NOCLOSE "WARNING: unable to close <%s>" 68 #define ERR_NOUNLINK_LATENT "WARNING: unable to unlink latent <%s>" 69 #define ERR_LINK_FAIL "link(%s, %s) failed (errno %d)" 70 #define ERR_NORENAME_CONTENTS "unable to establish contents file <%s> "\ 71 "from <%s>" 72 #define ERR_RENAME_FAIL "rename(%s, %s) failed (errno %d)" 73 #define ERR_RESTORE_FAIL "attempt to restore <%s> failed" 74 #define ERR_NOUNLINK "WARNING: unable to unlink <%s>" 75 #define ERR_FCLOSE_FAIL "fclose failed (errno %d)" 76 #define ERR_ERRNO "(errno %d: %s)" 77 #define ERR_NOTMPOPEN "unable to open temporary contents file image" 78 #define ERR_CFBACK "Not enough space to backup <%s>" 79 #define ERR_CREAT_CONT "unable to create contents file <%s>: %s" 80 #define ERR_ACCESS_CONT "unable to access contents file <%s>: %s" 81 #define ERR_CFBACK1 "Need=%llu blocks, Available=%llu blocks " \ 82 "(block size=%d)" 83 #define ERR_NOCFILE "unable to locate contents file <%s>" 84 #define ERR_NOROPEN "unable to open <%s> for reading" 85 #define ERR_NOOPEN "unable to open <%s> for writing" 86 #define ERR_NOSTAT "unable to stat contents file <%s>" 87 #define ERR_NOSTATV "statvfs(%s) failed" 88 #define ERR_NOUPD "unable to update contents file" 89 #define ERR_DRCONTCP "unable to copy contents file to <%s>" 90 91 #define MSG_XWTING "NOTE: Waiting for exclusive access to the package " \ 92 "database." 93 #define MSG_NOLOCK "NOTE: Couldn't lock the package database." 94 95 #define ERR_NOLOCK "Database lock failed." 96 #define ERR_OPLOCK "unable to open lock file <%s>." 97 #define ERR_MKLOCK "unable to create lock file <%s>." 98 #define ERR_LCKREM "unable to lock package database - remote host " \ 99 "unavailable." 100 #define ERR_BADLCK "unable to lock package database - unknown error." 101 #define ERR_DEADLCK "unable to lock package database - deadlock condition." 102 #define ERR_TMOUT "unable to lock package database - too many retries." 103 #define ERR_CFDIR "unable to locate contents file directory" 104 105 static int active_lock; 106 static int lock_fd; /* fd of LOCKFILE. */ 107 static char *pkgadm_dir; 108 109 int pkgWlock(int verbose); 110 static int pkgWunlock(void); 111 112 /* forward declarations */ 113 114 int relslock(void); 115 116 /*ARGSUSED*/ 117 static void 118 do_alarm(int n) 119 { 120 (void) signal(SIGALRM, SIG_IGN); 121 (void) signal(SIGALRM, do_alarm); 122 (void) alarm(LOCKWAIT); 123 } 124 125 /* 126 * Point packaging to the appropriate contents file. This is primarily used 127 * to establish a dryrun contents file. If the malloc() doesn't work, this 128 * returns 99 (internal error), else 0. 129 */ 130 int 131 set_cfdir(char *cfdir) 132 { 133 char realcf[PATH_MAX]; 134 char tmpcf[PATH_MAX]; 135 int status; 136 137 if (cfdir == NULL) { 138 pkgadm_dir = get_PKGADM(); 139 return (0); 140 } 141 142 if ((pkgadm_dir = strdup(cfdir)) == NULL) { 143 return (99); 144 } 145 146 (void) snprintf(tmpcf, sizeof (tmpcf), "%s/contents", pkgadm_dir); 147 148 /* 149 * return if a temporary contents file already exists - 150 * assume it is from a prior package in this series. 151 */ 152 153 if (access(tmpcf, F_OK) == 0) { 154 return (0); 155 } 156 157 /* 158 * no temporary contents file exists - create one. 159 */ 160 161 (void) snprintf(realcf, sizeof (realcf), "%s/contents", get_PKGADM()); 162 163 /* 164 * If there's a contents file there already, copy it 165 * over, otherwise initialize one. Make sure that the 166 * server, if running, flushes the contents file. 167 */ 168 169 (void) pkgsync(NULL, get_PKGADM(), B_FALSE); 170 171 /* create new contents file if one does not already exist */ 172 173 if (access(realcf, F_OK) != 0) { 174 int n; 175 176 n = open(tmpcf, O_WRONLY|O_CREAT|O_TRUNC|O_EXCL, 0644); 177 if (n < 0) { 178 progerr(gettext(ERR_CREAT_CONT), tmpcf, 179 strerror(errno)); 180 return (99); 181 } 182 (void) close(n); 183 } else { 184 185 /* contents file exists, save in pkgadm-dir */ 186 187 status = copyf(realcf, tmpcf, (time_t)0); 188 if (status != 0) { 189 progerr(gettext(ERR_DRCONTCP), tmpcf); 190 return (99); 191 } 192 } 193 194 return (0); 195 } 196 197 /* 198 * This function installs the database lock, opens the contents file for 199 * reading and creates and opens the temporary contents file for read/write. 200 * It returns 1 if successful, 0 otherwise. 201 */ 202 int 203 ocfile(PKGserver *server, VFP_T **r_tmpvfp, fsblkcnt_t map_blks) 204 { 205 struct stat64 statb, statl; 206 struct statvfs64 svfsb; 207 fsblkcnt_t free_blocks; 208 fsblkcnt_t need_blocks; 209 fsblkcnt_t log_blocks; 210 VFP_T *tmpvfp = (VFP_T *)NULL; 211 char contents[PATH_MAX]; 212 char logfile[PATH_MAX]; 213 off_t cdiff_alloc; 214 PKGserver newserver; 215 216 /* establish package administration contents directory location */ 217 218 if (pkgadm_dir == NULL) { 219 if (set_cfdir(NULL) != 0) { 220 progerr(gettext(ERR_CFDIR)); 221 return (0); 222 } 223 } 224 225 /* Lock the file for exclusive access */ 226 227 if (!pkgWlock(1)) { 228 progerr(gettext(ERR_NOLOCK)); 229 return (0); 230 } 231 232 if (*server != NULL) { 233 vfpTruncate(*r_tmpvfp); 234 (void) vfpClearModified(*r_tmpvfp); 235 236 return (1); 237 } 238 239 newserver = pkgopenserver(NULL, pkgadm_dir, B_FALSE); 240 241 /* The error has been reported. */ 242 if (newserver == NULL) 243 return (0); 244 245 /* reset return VFP/FILE pointers */ 246 247 (*r_tmpvfp) = (VFP_T *)NULL; 248 249 /* determine path to the primary contents file */ 250 (void) snprintf(contents, sizeof (contents), "%s/contents", pkgadm_dir); 251 252 /* 253 * Check and see if there is enough space for the packaging commands 254 * to back up the contents file, if there is not, then do not allow 255 * execution to continue by failing the ocfile() call. 256 */ 257 258 /* Get the contents file size */ 259 260 if (stat64(contents, &statb) == -1) { 261 int lerrno = errno; 262 263 progerr(gettext(ERR_NOCFILE), contents); 264 logerr(gettext(ERR_ERRNO), lerrno, strerror(lerrno)); 265 pkgcloseserver(newserver); 266 return (0); 267 } 268 269 /* Get the filesystem space */ 270 271 if (statvfs64(contents, &svfsb) == -1) { 272 int lerrno = errno; 273 274 progerr(gettext(ERR_NOSTATV), contents); 275 logerr(gettext(ERR_ERRNO), lerrno, strerror(lerrno)); 276 pkgcloseserver(newserver); 277 return (0); 278 } 279 280 free_blocks = (((fsblkcnt_t)svfsb.f_frsize > 0) ? 281 howmany(svfsb.f_frsize, DEV_BSIZE) : 282 howmany(svfsb.f_bsize, DEV_BSIZE)) * svfsb.f_bfree; 283 284 /* determine blocks used by the logfile */ 285 (void) snprintf(logfile, sizeof (logfile), "%s/" PKGLOG, pkgadm_dir); 286 287 if (stat64(logfile, &statl) == -1) 288 log_blocks = 0; 289 else 290 log_blocks = nblk(statl.st_size, svfsb.f_bsize, svfsb.f_frsize); 291 292 /* 293 * Calculate the number of blocks we need to be able to operate on 294 * the contents file and the log file. 295 * When adding a package (map_blks > 0), we add the size of the 296 * pkgmap file times 1.5 as the pkgmap is a bit smaller then the 297 * lines added to the contents file. That data is written both to 298 * the new contents file and the log file (2 * 1.5 * map_blks). 299 * The new contents file is limited by the size of the current 300 * contents file and the increased log file. 301 * If we're removing a package, then the log might grow to the size 302 * of the full contents file but then the new contents file would 303 * be zero and so we only need to add the size of the contents file. 304 */ 305 need_blocks = map_blks * 3 + 306 /* Current log file */ 307 log_blocks + 308 /* Current contents file */ 309 nblk(statb.st_size, svfsb.f_bsize, svfsb.f_frsize); 310 311 if ((need_blocks + 10) > free_blocks) { 312 progerr(gettext(ERR_CFBACK), contents); 313 progerr(gettext(ERR_CFBACK1), need_blocks, free_blocks, 314 DEV_BSIZE); 315 pkgcloseserver(newserver); 316 return (0); 317 } 318 319 /* 320 * open the temporary contents file without a path name - this causes 321 * the "vfp" to be opened on in-memory storage only, the size of which 322 * is set following a successful return - this causes the temporary 323 * contents file to be maintained in memory only - if no changes are 324 * made as the primary contents file is processed, the in memory data 325 * is discarded and not written to the disk. 326 */ 327 328 if (vfpOpen(&tmpvfp, (char *)NULL, "w", VFP_NONE) != 0) { 329 int lerrno = errno; 330 331 progerr(gettext(ERR_NOTMPOPEN)); 332 logerr(gettext(ERR_ERRNO), lerrno, strerror(lerrno)); 333 pkgcloseserver(newserver); 334 return (0); 335 } 336 337 /* 338 * set size of allocation for temporary contents file - this sets the 339 * size of the in-memory buffer associated with the open vfp. 340 * We only store the new and changed entries. 341 * We allocate memory depending on the size of the pkgmap; it's not 342 * completely right but <some value + * 1.5 * map_blks * DEV_BSIZE> 343 * seems fine (an install adds the size if the name of the package.) 344 */ 345 346 cdiff_alloc = map_blks * DEV_BSIZE; 347 cdiff_alloc += cdiff_alloc/2; 348 if (cdiff_alloc < 1000000) 349 cdiff_alloc += 1000000; 350 351 if (vfpSetSize(tmpvfp, cdiff_alloc) != 0) { 352 int lerrno = errno; 353 354 progerr(gettext(ERR_NOTMPOPEN)); 355 logerr(gettext(ERR_ERRNO), lerrno, strerror(lerrno)); 356 (void) vfpClose(&tmpvfp); 357 pkgcloseserver(newserver); 358 return (0); 359 } 360 361 /* set return ->s to open server/vfps */ 362 363 (*r_tmpvfp) = tmpvfp; 364 *server = newserver; 365 366 return (1); /* All OK */ 367 } 368 369 /* 370 * This is a simple open and lock of the contents file. It doesn't create a 371 * temporary contents file and it doesn't need to do any space checking. 372 * Returns 1 for OK and 0 for "didn't do it". 373 */ 374 int 375 socfile(PKGserver *server, boolean_t quiet) 376 { 377 boolean_t readonly = B_FALSE; 378 PKGserver newserver; 379 380 if (pkgadm_dir == NULL) { 381 if (set_cfdir(NULL) != 0) { 382 progerr(gettext(ERR_CFDIR)); 383 return (0); 384 } 385 } 386 387 /* 388 * Lock the database for exclusive access, but don't make a fuss if 389 * it fails (user may not be root and the .pkg.lock file may not 390 * exist yet). 391 */ 392 393 if (!pkgWlock(0)) { 394 if (!quiet) 395 logerr(gettext(MSG_NOLOCK)); 396 readonly = B_TRUE; 397 } 398 399 newserver = pkgopenserver(NULL, pkgadm_dir, readonly); 400 if (newserver == NULL) 401 return (0); 402 403 *server = newserver; 404 return (1); 405 } 406 407 /* 408 * Name: swapcfile 409 * Description: This function closes both the current and temporary contents 410 * files specified, and conditionally replaces the old transitory 411 * contents file with the newly updated temporary contents file. 412 * The "ocfile()" or "socfile()" functions must be called to re- 413 * open the real contents file for processing. 414 * Arguments: PKGserver - handle to the package database 415 * a_cfTmpVfp - (VFP_T **) - [RW, *RW] 416 * This is the VFP associated which contains all the 417 * modifications to be written back to the database. 418 * file that is being written to. 419 * pkginst - (char) - [RO, *RO] 420 * This is the name of the package being operated on; 421 * this is used to write the "last modified by xxx" 422 * comment at the end of the contents file. 423 * dbchg - (int) - [RO] 424 * == 0 - the temporary contents file has NOT been changed 425 * with respect to the real contents file; do not 426 * update the real contents file with the contents 427 * of the temporary contents file. 428 * != 0 - the temporary contetns file HAS been changed with 429 * respect to the real contents file; DO update the 430 * real contents file with the contents of the 431 * temporary contents file. 432 * Returns: int == RESULT_OK - successful 433 * == RESULT_WRN - successful with warnings 434 * == RESULT_ERR - failed with fatal errors - deserves an 435 * alarming message and a quit() 436 * NOTES: If dbchg != 0, the contents file is always updated. If dbchg == 0, 437 * the contents file is updated IF the data is modified indication 438 * is set on the contents file associated with a_cfTmpVfp. 439 */ 440 441 int 442 swapcfile(PKGserver server, VFP_T **a_cfTmpVfp, char *pkginst, int dbchg) 443 { 444 char *pe; 445 char *pl; 446 char *ps; 447 char line[256]; 448 char timeb[BUFSIZ]; 449 int retval = RESULT_OK; 450 struct tm *timep; 451 time_t clock; 452 453 /* normalize pkginst so its never null */ 454 455 if (pkginst == (char *)NULL) { 456 dbchg = 0; 457 pkginst = "<unknown>"; 458 } 459 460 /* 461 * If no changes were made to the database, checkpoint the temporary 462 * contents file - if this fails, then just close the file which causes 463 * the contents file to be reopened and reread if it is needed again 464 */ 465 466 if ((dbchg == 0) && (vfpGetModified(*a_cfTmpVfp) == 0)) { 467 (void) pkgWunlock(); /* Free the database lock. */ 468 return (retval); 469 } 470 471 /* 472 * changes made to the current temporary contents file - 473 * remove any trailing comment lines in the temp contents file, then 474 * append updated modification info records to temp contents file 475 */ 476 477 pe = vfpGetCurrCharPtr(*a_cfTmpVfp); /* last char in contents file */ 478 ps = vfpGetFirstCharPtr(*a_cfTmpVfp); /* 1st char in contents file */ 479 pl = pe; /* last match is last char in contents file */ 480 481 /* skip past all trailing newlines and null bytes */ 482 483 while ((pe > ps) && ((*pe == '\n') || (*pe == '\0'))) { 484 pe--; 485 } 486 487 /* remove trailing comments as long as there are lines in the file */ 488 489 while (pe > ps) { 490 if (*pe != '\n') { 491 /* curr char is not newline: backup one byte */ 492 pl = pe--; 493 } else if (*pl != '#') { 494 /* curr char is newline next char not comment break */ 495 break; 496 } else { 497 /* curr char is newline next char is comment - remove */ 498 *pl = '\0'; 499 vfpSetLastCharPtr(*a_cfTmpVfp, pl); 500 pe--; 501 } 502 } 503 504 /* create two update comment lines */ 505 506 (void) time(&clock); 507 timep = localtime(&clock); 508 509 (void) strftime(timeb, sizeof (timeb), "%c\n", timep); 510 (void) snprintf(line, sizeof (line), 511 gettext("# Last modified by %s for %s package\n# %s"), 512 get_prog_name(), pkginst, timeb); 513 vfpPuts(*a_cfTmpVfp, line); 514 515 /* commit temporary contents file bytes to storage */ 516 517 if (pkgservercommitfile(*a_cfTmpVfp, server) != 0) { 518 logerr(gettext(ERR_COMMIT)); 519 vfpClose(a_cfTmpVfp); 520 pkgcloseserver(server); 521 (void) pkgWunlock(); /* Free the database lock. */ 522 return (RESULT_ERR); 523 } 524 525 return (relslock() == 0 ? RESULT_ERR : retval); 526 } 527 528 /* This function releases the lock on the package database. */ 529 int 530 relslock(void) 531 { 532 /* 533 * This closes the contents file and releases the lock. 534 */ 535 if (!pkgWunlock()) { 536 int lerrno = errno; 537 538 progerr(gettext(ERR_NOUPD)); 539 logerr(gettext(ERR_FCLOSE_FAIL), lerrno); 540 return (0); 541 } 542 return (1); 543 } 544 545 /* 546 * This function attempts to lock the package database. It returns 1 on 547 * success, 0 on failure. The positive logic verbose flag determines whether 548 * or not the function displays the error message upon failure. 549 */ 550 int 551 pkgWlock(int verbose) 552 { 553 int retry_cnt, retval; 554 char lockpath[PATH_MAX]; 555 556 active_lock = 0; 557 558 (void) snprintf(lockpath, sizeof (lockpath), 559 "%s/%s", pkgadm_dir, LOCKFILE); 560 561 retry_cnt = LOCKRETRY; 562 563 /* 564 * If the lock file is not present, create it. The mode is set to 565 * allow any process to lock the database, that's because pkgchk may 566 * be run by a non-root user. 567 */ 568 if (access(lockpath, F_OK) == -1) { 569 lock_fd = open(lockpath, O_RDWR|O_CREAT|O_TRUNC|O_EXCL, 0644); 570 if (lock_fd < 0) { 571 if (verbose) 572 progerr(gettext(ERR_MKLOCK), lockpath); 573 return (0); 574 } else { 575 (void) fchmod(lock_fd, 0644); /* force perms. */ 576 } 577 } else { 578 if ((lock_fd = open(lockpath, O_RDWR)) == -1) { 579 if (verbose) 580 progerr(gettext(ERR_OPLOCK), lockpath); 581 return (0); 582 } 583 } 584 585 (void) signal(SIGALRM, do_alarm); 586 (void) alarm(LOCKWAIT); 587 588 do { 589 if (lockf(lock_fd, F_LOCK, 0)) { 590 if (errno == EAGAIN || errno == EINTR) 591 logerr(gettext(MSG_XWTING)); 592 else if (errno == ECOMM) { 593 logerr(gettext(ERR_LCKREM)); 594 retval = 0; 595 break; 596 } else if (errno == EBADF) { 597 logerr(gettext(ERR_BADLCK)); 598 retval = 0; 599 break; 600 } else if (errno == EDEADLK) { 601 logerr(gettext(ERR_DEADLCK)); 602 retval = 0; 603 break; 604 } 605 } else { 606 active_lock = 1; 607 retval = 1; 608 break; 609 } 610 } while (retry_cnt--); 611 612 (void) signal(SIGALRM, SIG_IGN); 613 614 if (retval == 0) { 615 if (retry_cnt == -1) { 616 logerr(gettext(ERR_TMOUT)); 617 } 618 619 (void) pkgWunlock(); /* close the lockfile. */ 620 } 621 622 return (retval); 623 } 624 625 /* 626 * Release the lock on the package database. Returns 1 on success, 0 on 627 * failure. 628 */ 629 static int 630 pkgWunlock(void) 631 { 632 if (active_lock) { 633 active_lock = 0; 634 if (close(lock_fd)) 635 return (0); 636 else 637 return (1); 638 } else 639 return (1); 640 } 641 642 /* 643 * This function verifies that the contents file is in place. 644 * returns 1 - if it exists 645 * returns 0 - if it does not exist 646 */ 647 int 648 iscfile(void) 649 { 650 char contents[PATH_MAX]; 651 652 (void) snprintf(contents, PATH_MAX, "%s/contents", get_PKGADM()); 653 654 return (access(contents, F_OK) == 0 ? 1 : 0); 655 } 656 657 /* 658 * This function verifies that the contents file is in place. If it is - no 659 * change. If it isn't - this creates it. 660 * Returns: == 0 : failure 661 * != 0 : success 662 */ 663 664 int 665 vcfile(void) 666 { 667 int lerrno; 668 int fd; 669 char contents[PATH_MAX]; 670 671 /* 672 * create full path to contents file 673 */ 674 675 (void) snprintf(contents, sizeof (contents), 676 "%s/contents", get_PKGADM()); 677 678 /* 679 * Attempt to create the file - will only be successful 680 * if the file does not currently exist. 681 */ 682 683 fd = open(contents, O_WRONLY | O_CREAT | O_EXCL, 0644); 684 if (fd >= 0) { 685 /* 686 * Contents file wasn't there, but is now. 687 */ 688 689 echo(gettext("## Software contents file initialized")); 690 (void) close(fd); 691 return (1); /* success */ 692 } 693 694 /* 695 * Could not create the file - it may exist or there may be 696 * permissions issues - find out and act accordingly. 697 */ 698 699 lerrno = errno; 700 701 /* success if error is 'file exists' */ 702 703 if (lerrno == EEXIST) { 704 return (1); /* success */ 705 } 706 707 /* success if error is 'permission denied' but file exists */ 708 709 if (lerrno == EACCES) { 710 /* 711 * Because O_CREAT and O_EXCL are specified in open(), 712 * if the contents file already exists, the open will 713 * fail with EACCES - determine if this is the case - 714 * if so return success. 715 */ 716 717 if (access(contents, F_OK) == 0) { 718 return (1); /* success */ 719 } 720 721 /* 722 * access() failed - if because of permissions failure this 723 * means the contents file exists but it cannot be accessed 724 * or the path to the contents file cannot be accessed - in 725 * either case the contents file cannot be accessed. 726 */ 727 728 if (errno == EACCES) { 729 progerr(gettext(ERR_ACCESS_CONT), contents, 730 strerror(lerrno)); 731 logerr(gettext(ERR_ERRNO), lerrno, strerror(lerrno)); 732 return (0); /* failure */ 733 } 734 } 735 736 /* 737 * the contents file does not exist and it cannot be created. 738 */ 739 740 progerr(gettext(ERR_CREAT_CONT), contents, strerror(lerrno)); 741 logerr(gettext(ERR_ERRNO), lerrno, strerror(lerrno)); 742 return (0); /* failure */ 743 } 744