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