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
105static int	active_lock;
106static int	lock_fd;	/* fd of LOCKFILE. */
107static char	*pkgadm_dir;
108
109int		pkgWlock(int verbose);
110static int	pkgWunlock(void);
111
112/* forward declarations */
113
114int relslock(void);
115
116/*ARGSUSED*/
117static void
118do_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 */
130int
131set_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 */
202int
203ocfile(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 */
374int
375socfile(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
441int
442swapcfile(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. */
529int
530relslock(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 */
550int
551pkgWlock(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 */
629static int
630pkgWunlock(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 */
647int
648iscfile(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
664int
665vcfile(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