xref: /titanic_44/usr/src/cmd/halt/halt.c (revision fb3fb4f3d76d55b64440afd0af72775dfad3bd1d)
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, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 /*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
28 /*	  All Rights Reserved  	*/
29 
30 /*
31  * University Copyright- Copyright (c) 1982, 1986, 1988
32  * The Regents of the University of California
33  * All Rights Reserved
34  *
35  * University Acknowledgment- Portions of this document are derived from
36  * software developed by the University of California, Berkeley, and its
37  * contributors.
38  */
39 
40 #pragma ident	"%Z%%M%	%I%	%E% SMI"
41 
42 /*
43  * Common code for halt(1M), poweroff(1M), and reboot(1M).  We use
44  * argv[0] to determine which behavior to exhibit.
45  */
46 
47 #include <sys/stat.h>
48 #include <sys/types.h>
49 #include <sys/uadmin.h>
50 #include <alloca.h>
51 #include <assert.h>
52 #include <errno.h>
53 #include <fcntl.h>
54 #include <libgen.h>
55 #include <libscf.h>
56 #include <locale.h>
57 #include <libintl.h>
58 #include <syslog.h>
59 #include <signal.h>
60 #include <strings.h>
61 #include <unistd.h>
62 #include <stdlib.h>
63 #include <stdio.h>
64 #include <strings.h>
65 #include <time.h>
66 #include <utmpx.h>
67 #include <pwd.h>
68 #include <zone.h>
69 #if !defined(TEXT_DOMAIN)
70 #define	TEXT_DOMAIN	"SYS_TEST"
71 #endif
72 
73 extern int audit_halt_setup(int, char **);
74 extern int audit_halt_success(void);
75 extern int audit_halt_fail(void);
76 
77 extern int audit_reboot_setup(void);
78 extern int audit_reboot_success(void);
79 extern int audit_reboot_fail(void);
80 
81 typedef struct ctidlist_struct {
82 	ctid_t ctid;
83 	struct ctidlist_struct *next;
84 } ctidlist_t;
85 
86 static ctidlist_t *ctidlist = NULL;
87 static ctid_t startdct = -1;
88 
89 #define	FMRI_STARTD_CONTRACT \
90 	"svc:/system/svc/restarter:default/:properties/restarter/contract"
91 
92 #define	ZONEADM_PROG "/usr/sbin/zoneadm"
93 
94 static void
95 stop_startd()
96 {
97 	ctid_t ctid;
98 
99 	scf_handle_t *h;
100 	scf_property_t *prop = NULL;
101 	scf_value_t *val = NULL;
102 	uint64_t uint64;
103 	int ret;
104 
105 	h = scf_handle_create(SCF_VERSION);
106 	if (h == NULL)
107 		return;
108 
109 	ret = scf_handle_bind(h);
110 	if (ret) {
111 		scf_handle_destroy(h);
112 		return;
113 	}
114 
115 	prop = scf_property_create(h);
116 	val = scf_value_create(h);
117 
118 	if (!(prop && val))
119 		goto out;
120 
121 	ret = scf_handle_decode_fmri(h, FMRI_STARTD_CONTRACT,
122 	    NULL, NULL, NULL, NULL, prop, SCF_DECODE_FMRI_EXACT);
123 	if (ret)
124 		goto out;
125 
126 	ret = scf_property_is_type(prop, SCF_TYPE_COUNT);
127 	if (ret)
128 		goto out;
129 
130 	ret = scf_property_get_value(prop, val);
131 	if (ret)
132 		goto out;
133 
134 	ret = scf_value_get_count(val, &uint64);
135 	if (ret)
136 		goto out;
137 
138 	ctid = (ctid_t)uint64;
139 	startdct = ctid;
140 	(void) sigsend(P_CTID, ctid, SIGSTOP);
141 
142 out:
143 	if (prop)
144 		scf_property_destroy(prop);
145 	if (val)
146 		scf_value_destroy(val);
147 
148 	(void) scf_handle_unbind(h);
149 	scf_handle_destroy(h);
150 }
151 
152 static void
153 continue_startd()
154 {
155 	if (startdct != -1)
156 		(void) sigsend(P_CTID, startdct, SIGCONT);
157 }
158 
159 #define	FMRI_RESTARTER_PROP "/:properties/general/restarter"
160 #define	FMRI_CONTRACT_PROP "/:properties/restarter/contract"
161 
162 static int
163 save_ctid(ctid_t ctid)
164 {
165 	ctidlist_t *next;
166 
167 	for (next = ctidlist; next != NULL; next = next->next)
168 		if (next->ctid == ctid)
169 			return (-1);
170 
171 	next = (ctidlist_t *)malloc(sizeof (ctidlist_t));
172 	if (next == NULL)
173 		return (-1);
174 
175 	next->ctid = ctid;
176 	next->next = ctidlist;
177 	ctidlist = next;
178 	return (0);
179 }
180 
181 static void
182 stop_delegates()
183 {
184 	ctid_t ctid;
185 	scf_handle_t *h;
186 	scf_scope_t *sc = NULL;
187 	scf_service_t *svc = NULL;
188 	scf_instance_t *inst = NULL;
189 	scf_snapshot_t *snap = NULL;
190 	scf_snapshot_t *isnap = NULL;
191 	scf_propertygroup_t *pg = NULL;
192 	scf_property_t *prop = NULL;
193 	scf_value_t *val = NULL;
194 	scf_iter_t *siter = NULL;
195 	scf_iter_t *iiter = NULL;
196 	char *fmri;
197 	ssize_t length;
198 
199 	uint64_t uint64;
200 	ssize_t bytes;
201 	int ret;
202 
203 	length = scf_limit(SCF_LIMIT_MAX_FMRI_LENGTH);
204 	if (length <= 0)
205 		return;
206 
207 	length++;
208 	fmri = alloca(length * sizeof (char));
209 
210 	h = scf_handle_create(SCF_VERSION);
211 	if (!h)
212 		return;
213 
214 	ret = scf_handle_bind(h);
215 	if (ret) {
216 		scf_handle_destroy(h);
217 		return;
218 	}
219 
220 	sc = scf_scope_create(h);
221 	svc = scf_service_create(h);
222 	inst = scf_instance_create(h);
223 	snap = scf_snapshot_create(h);
224 	pg = scf_pg_create(h);
225 	prop = scf_property_create(h);
226 	val = scf_value_create(h);
227 	siter = scf_iter_create(h);
228 	iiter = scf_iter_create(h);
229 
230 	if (!(sc && svc && inst && snap &&
231 	    pg && prop && val && siter && iiter))
232 		goto out;
233 
234 	ret = scf_handle_get_scope(h, SCF_SCOPE_LOCAL, sc);
235 	if (ret)
236 		goto out;
237 
238 	ret = scf_iter_scope_services(siter, sc);
239 	if (ret)
240 		goto out;
241 
242 	while (scf_iter_next_service(siter, svc) == 1) {
243 
244 		ret = scf_iter_service_instances(iiter, svc);
245 		if (ret)
246 			continue;
247 
248 		while (scf_iter_next_instance(iiter, inst) == 1) {
249 
250 			ret = scf_instance_get_snapshot(inst, "running", snap);
251 				if (ret)
252 					isnap = NULL;
253 				else
254 					isnap = snap;
255 
256 			ret = scf_instance_get_pg_composed(inst, isnap,
257 			    SCF_PG_GENERAL, pg);
258 			if (ret)
259 				continue;
260 
261 			ret = scf_pg_get_property(pg, "restarter", prop);
262 			if (ret)
263 				continue;
264 
265 			ret = scf_property_is_type(prop, SCF_TYPE_ASTRING);
266 			if (ret)
267 				continue;
268 
269 			ret = scf_property_get_value(prop, val);
270 			if (ret)
271 				continue;
272 
273 			bytes = scf_value_get_astring(val, fmri, length);
274 			if (bytes <= 0 || bytes >= length)
275 				continue;
276 
277 			if (strlcat(fmri, FMRI_CONTRACT_PROP, length) >=
278 			    length)
279 				continue;
280 
281 			ret = scf_handle_decode_fmri(h, fmri, NULL, NULL,
282 			    NULL, NULL, prop, SCF_DECODE_FMRI_EXACT);
283 			if (ret)
284 				continue;
285 
286 			ret = scf_property_is_type(prop, SCF_TYPE_COUNT);
287 			if (ret)
288 				continue;
289 
290 			ret = scf_property_get_value(prop, val);
291 			if (ret)
292 				continue;
293 
294 			ret = scf_value_get_count(val, &uint64);
295 			if (ret)
296 				continue;
297 
298 			ctid = (ctid_t)uint64;
299 			if (save_ctid(ctid) == 0) {
300 				(void) sigsend(P_CTID, ctid, SIGSTOP);
301 			}
302 		}
303 	}
304 out:
305 	if (sc)
306 		scf_scope_destroy(sc);
307 	if (svc)
308 		scf_service_destroy(svc);
309 	if (inst)
310 		scf_instance_destroy(inst);
311 	if (snap)
312 		scf_snapshot_destroy(snap);
313 	if (pg)
314 		scf_pg_destroy(pg);
315 	if (prop)
316 		scf_property_destroy(prop);
317 	if (val)
318 		scf_value_destroy(val);
319 	if (siter)
320 		scf_iter_destroy(siter);
321 	if (iiter)
322 		scf_iter_destroy(iiter);
323 
324 	(void) scf_handle_unbind(h);
325 	scf_handle_destroy(h);
326 }
327 
328 static void
329 continue_delegates()
330 {
331 	ctidlist_t *next;
332 	for (next = ctidlist; next != NULL; next = next->next)
333 		(void) sigsend(P_CTID, next->ctid, SIGCONT);
334 }
335 
336 static void
337 stop_restarters()
338 {
339 	stop_startd();
340 	stop_delegates();
341 }
342 
343 static void
344 continue_restarters()
345 {
346 	continue_startd();
347 	continue_delegates();
348 }
349 
350 /*
351  * Copy an array of strings into buf, separated by spaces.  Returns 0 on
352  * success.
353  */
354 static int
355 gather_args(char **args, char *buf, size_t buf_sz)
356 {
357 	if (strlcpy(buf, *args, buf_sz) >= buf_sz)
358 		return (-1);
359 
360 	for (++args; *args != NULL; ++args) {
361 		if (strlcat(buf, " ", buf_sz) >= buf_sz)
362 			return (-1);
363 		if (strlcat(buf, *args, buf_sz) >= buf_sz)
364 			return (-1);
365 	}
366 
367 	return (0);
368 }
369 
370 /*
371  * Halt every zone on the system.  We are committed to doing a shutdown
372  * even if something goes wrong here. If something goes wrong, we just
373  * continue with the shutdown.  Return non-zero if we need to wait for zones to
374  * halt later on.
375  */
376 static int
377 halt_zones(const char *name)
378 {
379 	pid_t pid;
380 	zoneid_t *zones;
381 	size_t nz, old_nz;
382 	int i;
383 	char zname[ZONENAME_MAX];
384 
385 	/*
386 	 * Get a list of zones. If the number of zones changes in between the
387 	 * two zone_list calls, try again.
388 	 */
389 
390 	for (;;) {
391 		(void) zone_list(NULL, &nz);
392 		if (nz == 1)
393 			return (0);
394 		old_nz = nz;
395 		zones = calloc(sizeof (zoneid_t), nz);
396 		if (zones == NULL) {
397 			(void) fprintf(stderr,
398 			    gettext("%s: Could not halt zones"
399 			    " (out of memory).\n"), name);
400 			return (0);
401 		}
402 
403 		(void) zone_list(zones, &nz);
404 		if (old_nz == nz)
405 			break;
406 		free(zones);
407 	}
408 
409 	if (nz == 2) {
410 		(void) fprintf(stderr,
411 		    gettext("%s: Halting 1 zone.\n"),
412 		    name);
413 	} else {
414 		(void) fprintf(stderr,
415 		    gettext("%s: Halting %i zones.\n"),
416 		    name, nz - 1);
417 	}
418 
419 	for (i = 0; i < nz; i++) {
420 		if (zones[i] == GLOBAL_ZONEID)
421 			continue;
422 		if (getzonenamebyid(zones[i], zname, sizeof (zname)) < 0) {
423 			/*
424 			 * getzonenamebyid should only fail if we raced with
425 			 * another process trying to shut down the zone.
426 			 * We assume this happened and ignore the error.
427 			 */
428 			if (errno != EINVAL) {
429 				(void) fprintf(stderr,
430 				    gettext("%s: Unexpected error while "
431 				    "looking up zone %ul: %s.\n"),
432 				    name, zones[i], strerror(errno));
433 			}
434 
435 			continue;
436 		}
437 		pid = fork();
438 		if (pid < 0) {
439 			(void) fprintf(stderr,
440 			    gettext("%s: Zone \"%s\" could not be"
441 			    " halted (could not fork(): %s).\n"),
442 			    name, zname, strerror(errno));
443 			continue;
444 		}
445 		if (pid == 0) {
446 			(void) execl(ZONEADM_PROG, ZONEADM_PROG,
447 			    "-z", zname, "halt", NULL);
448 			(void) fprintf(stderr,
449 			    gettext("%s: Zone \"%s\" could not be halted"
450 			    " (cannot exec(" ZONEADM_PROG "): %s).\n"),
451 			    name, zname, strerror(errno));
452 			exit(0);
453 		}
454 	}
455 
456 	return (1);
457 }
458 
459 /*
460  * This function tries to wait for all non-global zones to go away.
461  * It will timeout if no progress is made for 5 seconds, or a total of
462  * 30 seconds elapses.
463  */
464 
465 static void
466 check_zones_haltedness(const char *name)
467 {
468 	int t = 0, t_prog = 0;
469 	size_t nz = 0, last_nz;
470 
471 	do {
472 		last_nz = nz;
473 		(void) zone_list(NULL, &nz);
474 		if (nz == 1)
475 			return;
476 
477 		(void) sleep(1);
478 
479 		if (last_nz > nz)
480 			t_prog = 0;
481 
482 		t++;
483 		t_prog++;
484 
485 		if (t == 10) {
486 			if (nz == 2) {
487 				(void) fprintf(stderr,
488 				    gettext("%s: Still waiting for 1 zone to "
489 				    "halt. Will wait up to 20 seconds.\n"),
490 				    name);
491 			} else {
492 				(void) fprintf(stderr,
493 				    gettext("%s: Still waiting for %i zones "
494 				    "to halt. Will wait up to 20 seconds.\n"),
495 				    name, nz - 1);
496 			}
497 		}
498 
499 	} while ((t < 30) && (t_prog < 5));
500 }
501 
502 int
503 main(int argc, char *argv[])
504 {
505 	char *cmdname = basename(argv[0]);
506 	char *ttyn = ttyname(STDERR_FILENO);
507 
508 	int qflag = 0, needlog = 1, nosync = 0;
509 	uintptr_t mdep = NULL;
510 	int cmd, fcn, c, aval, r;
511 	const char *usage;
512 	zoneid_t zoneid = getzoneid();
513 	pid_t init_pid = 1;
514 	int need_check_zones;
515 
516 	char bootargs_buf[257];		/* uadmin()'s buffer is 257 bytes. */
517 
518 	const char * const resetting = "/etc/svc/volatile/resetting";
519 
520 
521 	(void) setlocale(LC_ALL, "");
522 	(void) textdomain(TEXT_DOMAIN);
523 
524 	if (strcmp(cmdname, "halt") == 0) {
525 		(void) audit_halt_setup(argc, argv);
526 		usage = gettext("usage: %s [ -dlnqy ]\n");
527 		cmd = A_SHUTDOWN;
528 		fcn = AD_HALT;
529 	} else if (strcmp(cmdname, "poweroff") == 0) {
530 		(void) audit_halt_setup(argc, argv);
531 		usage = gettext("usage: %s [ -dlnqy ]\n");
532 		cmd = A_SHUTDOWN;
533 		fcn = AD_POWEROFF;
534 	} else if (strcmp(cmdname, "reboot") == 0) {
535 		(void) audit_reboot_setup();
536 		usage = gettext("usage: %s [ -dlnq ] [ boot args ]\n");
537 		cmd = A_SHUTDOWN;
538 		fcn = AD_BOOT;
539 	} else {
540 		(void) fprintf(stderr,
541 		    gettext("%s: not installed properly\n"), cmdname);
542 		return (1);
543 	}
544 
545 	while ((c = getopt(argc, argv, "dlnqy")) != EOF) {
546 		switch (c) {
547 		case 'd':
548 			if (zoneid == GLOBAL_ZONEID)
549 				cmd = A_DUMP;
550 			else {
551 				(void) fprintf(stderr,
552 				    gettext("%s: -d only valid from global"
553 				    " zone\n"), cmdname);
554 				return (1);
555 			}
556 			break;
557 		case 'l':
558 			needlog = 0;
559 			break;
560 		case 'n':
561 			nosync = 1;
562 			break;
563 		case 'q':
564 			qflag = 1;
565 			break;
566 		case 'y':
567 			ttyn = NULL;
568 			break;
569 		default:
570 			/*
571 			 * TRANSLATION_NOTE
572 			 * Don't translate the words "halt" or "reboot"
573 			 */
574 			(void) fprintf(stderr, usage, cmdname);
575 			return (1);
576 		}
577 	}
578 
579 	argc -= optind;
580 	argv += optind;
581 
582 	if (argc != 0) {
583 		if (fcn != AD_BOOT) {
584 			(void) fprintf(stderr, usage, cmdname);
585 			return (1);
586 		}
587 
588 		/* Gather the arguments into bootargs_buf. */
589 		if (gather_args(argv, bootargs_buf, sizeof (bootargs_buf)) !=
590 		    0) {
591 			(void) fprintf(stderr,
592 			    gettext("%s: Boot arguments too long.\n"), cmdname);
593 			return (1);
594 		}
595 		mdep = (uintptr_t)bootargs_buf;
596 	}
597 
598 	if (geteuid() != 0) {
599 		(void) fprintf(stderr,
600 		    gettext("%s: permission denied\n"), cmdname);
601 		goto fail;
602 	}
603 
604 	if (fcn != AD_BOOT && ttyn != NULL &&
605 	    strncmp(ttyn, "/dev/term/", strlen("/dev/term/")) == 0) {
606 		/*
607 		 * TRANSLATION_NOTE
608 		 * Don't translate ``halt -y''
609 		 */
610 		(void) fprintf(stderr,
611 		    gettext("%s: dangerous on a dialup;"), cmdname);
612 		(void) fprintf(stderr,
613 		    gettext("use ``%s -y'' if you are really sure\n"), cmdname);
614 		goto fail;
615 	}
616 
617 	if (needlog) {
618 		char *user = getlogin();
619 		struct passwd *pw;
620 		char *tty;
621 
622 		openlog(cmdname, 0, LOG_AUTH);
623 		if (user == NULL && (pw = getpwuid(getuid())) != NULL)
624 			user = pw->pw_name;
625 		if (user == NULL)
626 			user = "root";
627 
628 		tty = ttyname(1);
629 
630 		if (tty == NULL)
631 			syslog(LOG_CRIT, "initiated by %s", user);
632 		else
633 			syslog(LOG_CRIT, "initiated by %s on %s", user, tty);
634 	}
635 
636 	/*
637 	 * We must assume success and log it before auditd is terminated.
638 	 */
639 	if (fcn == AD_BOOT)
640 		aval = audit_reboot_success();
641 	else
642 		aval = audit_halt_success();
643 
644 	if (aval == -1) {
645 		(void) fprintf(stderr,
646 		    gettext("%s: can't turn off auditd\n"), cmdname);
647 		if (needlog)
648 			(void) sleep(5); /* Give syslogd time to record this */
649 	}
650 
651 	(void) signal(SIGHUP, SIG_IGN);	/* for remote connections */
652 
653 	if (zone_getattr(getzoneid(), ZONE_ATTR_INITPID, &init_pid,
654 	    sizeof (init_pid)) != sizeof (init_pid)) {
655 		assert(errno == ESRCH);
656 		init_pid = -1;
657 	}
658 
659 	/*
660 	 * We start to fork a bunch of zoneadms to halt any active zones.
661 	 * This will proceed with halt in parallel until we call
662 	 * check_zone_haltedness later on.
663 	 */
664 	if (zoneid == GLOBAL_ZONEID && cmd != A_DUMP) {
665 		need_check_zones = halt_zones(cmdname);
666 	}
667 
668 
669 	/* sync boot archive in the global zone */
670 	if (getzoneid() == GLOBAL_ZONEID && !nosync) {
671 		(void) system("/sbin/bootadm -a update_all");
672 	}
673 
674 	/*
675 	 * If we're not forcing a crash dump, mark the system as quiescing for
676 	 * smf(5)'s benefit, and idle the init process.
677 	 */
678 	if (cmd != A_DUMP) {
679 		if (init_pid != -1 && kill(init_pid, SIGTSTP) == -1) {
680 			/*
681 			 * TRANSLATION_NOTE
682 			 * Don't translate the word "init"
683 			 */
684 			(void) fprintf(stderr,
685 			    gettext("%s: can't idle init\n"), cmdname);
686 
687 			goto fail;
688 		}
689 
690 		if (creat(resetting, 0755) == -1)
691 			(void) fprintf(stderr,
692 			    gettext("%s: could not create %s.\n"),
693 			    cmdname, resetting);
694 
695 		/*
696 		 * Stop all restarters so they do not try to restart services
697 		 * that are terminated.
698 		 */
699 		stop_restarters();
700 
701 		/*
702 		 * Wait a little while for zones to shutdown.
703 		 */
704 		if (need_check_zones) {
705 			check_zones_haltedness(cmdname);
706 
707 			(void) fprintf(stderr,
708 			    gettext("%s: Completing system halt.\n"),
709 			    cmdname);
710 		}
711 	}
712 
713 	/*
714 	 * Make sure we don't get stopped by a jobcontrol shell
715 	 * once we start killing everybody.
716 	 */
717 	(void) signal(SIGTSTP, SIG_IGN);
718 	(void) signal(SIGTTIN, SIG_IGN);
719 	(void) signal(SIGTTOU, SIG_IGN);
720 	(void) signal(SIGTERM, SIG_IGN);
721 
722 	/*
723 	 * If we're not forcing a crash dump, give everyone 5 seconds to
724 	 * handle a SIGTERM and clean up properly.
725 	 */
726 	if (cmd != A_DUMP) {
727 		(void) kill(-1, SIGTERM);
728 		(void) sleep(5);
729 	}
730 
731 	if (!qflag && !nosync) {
732 		struct utmpx wtmpx;
733 
734 		bzero(&wtmpx, sizeof (struct utmpx));
735 		(void) strcpy(wtmpx.ut_line, "~");
736 		(void) time(&wtmpx.ut_tv.tv_sec);
737 
738 		if (cmd == A_DUMP)
739 			(void) strcpy(wtmpx.ut_name, "crash dump");
740 		else
741 			(void) strcpy(wtmpx.ut_name, "shutdown");
742 
743 		(void) updwtmpx(WTMPX_FILE, &wtmpx);
744 		sync();
745 	}
746 
747 	if (cmd == A_DUMP && nosync != 0)
748 		(void) uadmin(A_DUMP, AD_NOSYNC, NULL);
749 
750 	(void) uadmin(cmd, fcn, mdep);
751 	perror(cmdname);
752 	do
753 		r = remove(resetting);
754 	while (r != 0 && errno == EINTR);
755 	if (r != 0 && errno != ENOENT)
756 		(void) fprintf(stderr, gettext("%s: could not remove %s.\n"),
757 		    cmdname, resetting);
758 
759 	continue_restarters();
760 
761 	if (init_pid != -1)
762 		/* tell init to restate current level */
763 		(void) kill(init_pid, SIGHUP);
764 
765 fail:
766 	if (fcn == AD_BOOT)
767 		(void) audit_reboot_fail();
768 	else
769 		(void) audit_halt_fail();
770 
771 	return (1);
772 }
773