xref: /freebsd/contrib/tcsh/tc.func.c (revision 56ca39961bd1c9946a505c41c3fc634ef63fdd42)
1 /* $Header: /src/pub/tcsh/tc.func.c,v 3.87 1999/08/14 21:24:13 christos Exp $ */
2 /*
3  * tc.func.c: New tcsh builtins.
4  */
5 /*-
6  * Copyright (c) 1980, 1991 The Regents of the University of California.
7  * All rights reserved.
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions
11  * are met:
12  * 1. Redistributions of source code must retain the above copyright
13  *    notice, this list of conditions and the following disclaimer.
14  * 2. Redistributions in binary form must reproduce the above copyright
15  *    notice, this list of conditions and the following disclaimer in the
16  *    documentation and/or other materials provided with the distribution.
17  * 3. All advertising materials mentioning features or use of this software
18  *    must display the following acknowledgement:
19  *	This product includes software developed by the University of
20  *	California, Berkeley and its contributors.
21  * 4. Neither the name of the University nor the names of its contributors
22  *    may be used to endorse or promote products derived from this software
23  *    without specific prior written permission.
24  *
25  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
26  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
27  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
28  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
29  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
30  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
31  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
32  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
33  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
34  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
35  * SUCH DAMAGE.
36  */
37 #include "sh.h"
38 
39 RCSID("$Id: tc.func.c,v 3.87 1999/08/14 21:24:13 christos Exp $")
40 
41 #include "ed.h"
42 #include "ed.defns.h"		/* for the function names */
43 #include "tw.h"
44 #include "tc.h"
45 #ifdef WINNT
46 #include "nt.const.h"
47 #endif /* WINNT */
48 
49 #ifdef AFS
50 #define PASSMAX 16
51 #include <afs/stds.h>
52 #include <afs/kautils.h>
53 long ka_UserAuthenticateGeneral();
54 #else
55 #ifndef PASSMAX
56 #define PASSMAX 8
57 #endif
58 #endif /* AFS */
59 
60 #ifdef TESLA
61 extern int do_logout;
62 #endif /* TESLA */
63 extern time_t t_period;
64 extern int just_signaled;
65 static bool precmd_active = 0;
66 static bool postcmd_active = 0;
67 static bool periodic_active = 0;
68 static bool cwdcmd_active = 0;	/* PWP: for cwd_cmd */
69 static bool beepcmd_active = 0;
70 static signalfun_t alm_fun = NULL;
71 
72 static	void	 auto_logout	__P((int));
73 static	char	*xgetpass	__P((char *));
74 static	void	 auto_lock	__P((int));
75 #ifdef BSDJOBS
76 static	void	 insert		__P((struct wordent *, bool));
77 static	void	 insert_we	__P((struct wordent *, struct wordent *));
78 static	int	 inlist		__P((Char *, Char *));
79 #endif /* BSDJOBS */
80 struct tildecache;
81 static	int	 tildecompare	__P((struct tildecache *, struct tildecache *));
82 static  Char    *gethomedir	__P((Char *));
83 #ifdef REMOTEHOST
84 static	sigret_t palarm		__P((int));
85 static	void	 getremotehost	__P((void));
86 #endif /* REMOTEHOST */
87 
88 /*
89  * Tops-C shell
90  */
91 
92 /*
93  * expand_lex: Take the given lex and put an expanded version of it in the
94  * string buf. First guy in lex list is ignored; last guy is ^J which we
95  * ignore Only take lex'es from position from to position to inclusive Note:
96  * csh sometimes sets bit 8 in characters which causes all kinds of problems
97  * if we don't mask it here. Note: excl's in lexes have been un-back-slashed
98  * and must be re-back-slashed
99  * (PWP: NOTE: this returns a pointer to the END of the string expanded
100  *             (in other words, where the NUL is).)
101  */
102 /* PWP: this is a combination of the old sprlex() and the expand_lex from
103    the magic-space stuff */
104 
105 Char   *
106 expand_lex(buf, bufsiz, sp0, from, to)
107     Char   *buf;
108     size_t  bufsiz;
109     struct  wordent *sp0;
110     int     from, to;
111 {
112     register struct wordent *sp;
113     register Char *s, *d, *e;
114     register Char prev_c;
115     register int i;
116 
117     buf[0] = '\0';
118     prev_c = '\0';
119     d = buf;
120     e = &buf[bufsiz];		/* for bounds checking */
121 
122     if (!sp0)
123 	return (buf);		/* null lex */
124     if ((sp = sp0->next) == sp0)
125 	return (buf);		/* nada */
126     if (sp == (sp0 = sp0->prev))
127 	return (buf);		/* nada */
128 
129     for (i = 0; i < NCARGS; i++) {
130 	if ((i >= from) && (i <= to)) {	/* if in range */
131 	    for (s = sp->word; *s && d < e; s++) {
132 		/*
133 		 * bugfix by Michael Bloom: anything but the current history
134 		 * character {(PWP) and backslash} seem to be dealt with
135 		 * elsewhere.
136 		 */
137 		if ((*s & QUOTE)
138 		    && (((*s & TRIM) == HIST) ||
139 			(((*s & TRIM) == '\'') && (prev_c != '\\')) ||
140 			(((*s & TRIM) == '\"') && (prev_c != '\\')) ||
141 			(((*s & TRIM) == '\\') && (prev_c != '\\')))) {
142 		    *d++ = '\\';
143 		}
144 		if (d < e)
145 		    *d++ = (*s & TRIM);
146 		prev_c = *s;
147 	    }
148 	    if (d < e)
149 		*d++ = ' ';
150 	}
151 	sp = sp->next;
152 	if (sp == sp0)
153 	    break;
154     }
155     if (d > buf)
156 	d--;			/* get rid of trailing space */
157 
158     return (d);
159 }
160 
161 Char   *
162 sprlex(buf, bufsiz, sp0)
163     Char   *buf;
164     size_t  bufsiz;
165     struct wordent *sp0;
166 {
167     Char   *cp;
168 
169     cp = expand_lex(buf, bufsiz, sp0, 0, NCARGS);
170     *cp = '\0';
171     return (buf);
172 }
173 
174 
175 Char *
176 Itoa(n, s, min_digits, attributes)
177     int n;
178     Char *s;
179     int min_digits, attributes;
180 {
181     /*
182      * The array size here is derived from
183      *	log8(UINT_MAX)
184      * which is guaranteed to be enough for a decimal
185      * representation.  We add 1 because integer divide
186      * rounds down.
187      */
188 #ifndef CHAR_BIT
189 # define CHAR_BIT 8
190 #endif
191     Char buf[CHAR_BIT * sizeof(int) / 3 + 1];
192     Char *p;
193     unsigned int un;	/* handle most negative # too */
194     int pad = (min_digits != 0);
195 
196     if (sizeof(buf) - 1 < min_digits)
197 	min_digits = sizeof(buf) - 1;
198 
199     un = n;
200     if (n < 0) {
201 	un = -n;
202 	*s++ = '-';
203     }
204 
205     p = buf;
206     do {
207 	*p++ = un % 10 + '0';
208 	un /= 10;
209     } while ((pad && --min_digits > 0) || un != 0);
210 
211     while (p > buf)
212 	*s++ = *--p | attributes;
213 
214     *s = '\0';
215     return s;
216 }
217 
218 
219 /*ARGSUSED*/
220 void
221 dolist(v, c)
222     register Char **v;
223     struct command *c;
224 {
225     int     i, k;
226     struct stat st;
227 #if defined(KANJI) && defined(SHORT_STRINGS) && defined(DSPMBYTE)
228     extern bool dspmbyte_ls;
229 #endif
230 #ifdef COLOR_LS_F
231     extern bool color_context_ls;
232 #endif /* COLOR_LS_F */
233 
234     USE(c);
235     if (*++v == NULL) {
236 	(void) t_search(STRNULL, NULL, LIST, 0, TW_ZERO, 0, STRNULL, 0);
237 	return;
238     }
239     gflag = 0;
240     tglob(v);
241     if (gflag) {
242 	v = globall(v);
243 	if (v == 0)
244 	    stderror(ERR_NAME | ERR_NOMATCH);
245     }
246     else
247 	v = gargv = saveblk(v);
248     trim(v);
249     for (k = 0; v[k] != NULL && v[k][0] != '-'; k++)
250 	continue;
251     if (v[k]) {
252 	/*
253 	 * We cannot process a flag therefore we let ls do it right.
254 	 */
255 	static Char STRls[] = {'l', 's', '\0'};
256 	static Char STRmCF[] = {'-', 'C', 'F', '\0', '\0' };
257 	Char *lspath;
258 	struct command *t;
259 	struct wordent cmd, *nextword, *lastword;
260 	Char   *cp;
261 	struct varent *vp;
262 
263 #ifdef BSDSIGS
264 	sigmask_t omask = 0;
265 
266 	if (setintr)
267 	    omask = sigblock(sigmask(SIGINT)) & ~sigmask(SIGINT);
268 #else /* !BSDSIGS */
269 	(void) sighold(SIGINT);
270 #endif /* BSDSIGS */
271 	if (seterr) {
272 	    xfree((ptr_t) seterr);
273 	    seterr = NULL;
274 	}
275 
276 	lspath = STRls;
277 	STRmCF[1] = 'C';
278 	STRmCF[3] = '\0';
279 	/* Look at listflags, to add -A to the flags, to get a path
280 	   of ls if necessary */
281 	if ((vp = adrof(STRlistflags)) != NULL && vp->vec[0] != STRNULL) {
282 	    if (vp->vec[1] != NULL && vp->vec[1][0] != '\0')
283 		lspath = vp->vec[1];
284 	    for (cp = vp->vec[0]; *cp; cp++)
285 		switch (*cp) {
286 		case 'x':
287 		    STRmCF[1] = 'x';
288 		    break;
289 		case 'a':
290 		    STRmCF[3] = 'a';
291 		    break;
292 		case 'A':
293 		    STRmCF[3] = 'A';
294 		    break;
295 		default:
296 		    break;
297 		}
298 	}
299 
300 	cmd.word = STRNULL;
301 	lastword = &cmd;
302 	nextword = (struct wordent *) xcalloc(1, sizeof cmd);
303 	nextword->word = Strsave(lspath);
304 	lastword->next = nextword;
305 	nextword->prev = lastword;
306 	lastword = nextword;
307 	nextword = (struct wordent *) xcalloc(1, sizeof cmd);
308 	nextword->word = Strsave(STRmCF);
309 	lastword->next = nextword;
310 	nextword->prev = lastword;
311 #if defined(KANJI) && defined(SHORT_STRINGS) && defined(DSPMBYTE)
312 	if (dspmbyte_ls) {
313 	    lastword = nextword;
314 	    nextword = (struct wordent *) xcalloc(1, sizeof cmd);
315 	    nextword->word = Strsave(STRmmliteral);
316 	    lastword->next = nextword;
317 	    nextword->prev = lastword;
318 	}
319 #endif
320 #ifdef COLOR_LS_F
321 	if (color_context_ls) {
322 	    lastword = nextword;
323 	    nextword = (struct wordent *) xcalloc(1, sizeof cmd);
324 	    nextword->word = Strsave(STRmmcolormauto);
325 	    lastword->next = nextword;
326 	    nextword->prev = lastword;
327 	}
328 #endif /* COLOR_LS_F */
329 	lastword = nextword;
330 	for (cp = *v; cp; cp = *++v) {
331 	    nextword = (struct wordent *) xcalloc(1, sizeof cmd);
332 	    nextword->word = Strsave(cp);
333 	    lastword->next = nextword;
334 	    nextword->prev = lastword;
335 	    lastword = nextword;
336 	}
337 	lastword->next = &cmd;
338 	cmd.prev = lastword;
339 
340 	/* build a syntax tree for the command. */
341 	t = syntax(cmd.next, &cmd, 0);
342 	if (seterr)
343 	    stderror(ERR_OLD);
344 	/* expand aliases like process() does */
345 	/* alias(&cmd); */
346 	/* execute the parse tree. */
347 	execute(t, tpgrp > 0 ? tpgrp : -1, NULL, NULL);
348 	/* done. free the lex list and parse tree. */
349 	freelex(&cmd), freesyn(t);
350 	if (setintr)
351 #ifdef BSDSIGS
352 	    (void) sigsetmask(omask);
353 #else /* !BSDSIGS */
354 	    (void) sigrelse(SIGINT);
355 #endif /* BSDSIGS */
356     }
357     else {
358 	Char   *dp, *tmp, buf[MAXPATHLEN];
359 
360 	for (k = 0, i = 0; v[k] != NULL; k++) {
361 	    tmp = dnormalize(v[k], symlinks == SYM_IGNORE);
362 	    dp = &tmp[Strlen(tmp) - 1];
363 	    if (*dp == '/' && dp != tmp)
364 #ifdef apollo
365 		if (dp != &tmp[1])
366 #endif /* apollo */
367 		*dp = '\0';
368 		if (stat(short2str(tmp), &st) == -1) {
369 		if (k != i) {
370 		    if (i != 0)
371 			xputchar('\n');
372 		    print_by_column(STRNULL, &v[i], k - i, FALSE);
373 		}
374 		xprintf("%S: %s.\n", tmp, strerror(errno));
375 		i = k + 1;
376 	    }
377 	    else if (S_ISDIR(st.st_mode)) {
378 		Char   *cp;
379 
380 		if (k != i) {
381 		    if (i != 0)
382 			xputchar('\n');
383 		    print_by_column(STRNULL, &v[i], k - i, FALSE);
384 		}
385 		if (k != 0 && v[1] != NULL)
386 		    xputchar('\n');
387 		xprintf("%S:\n", tmp);
388 		for (cp = tmp, dp = buf; *cp; *dp++ = (*cp++ | QUOTE))
389 		    continue;
390 		if (
391 #ifdef WINNT
392 		    (dp[-1] != (Char) (':' | QUOTE)) &&
393 #endif /* WINNT */
394 		    (dp[-1] != (Char) ('/' | QUOTE)))
395 		    *dp++ = '/';
396 		else
397 		    dp[-1] &= TRIM;
398 		*dp = '\0';
399 		(void) t_search(buf, NULL, LIST, 0, TW_ZERO, 0, STRNULL, 0);
400 		i = k + 1;
401 	    }
402 	    xfree((ptr_t) tmp);
403 	}
404 	if (k != i) {
405 	    if (i != 0)
406 		xputchar('\n');
407 	    print_by_column(STRNULL, &v[i], k - i, FALSE);
408 	}
409     }
410 
411     if (gargv) {
412 	blkfree(gargv);
413 	gargv = 0;
414     }
415 }
416 
417 static char *defaulttell = "ALL";
418 extern bool GotTermCaps;
419 
420 /*ARGSUSED*/
421 void
422 dotelltc(v, c)
423     register Char **v;
424     struct command *c;
425 {
426     USE(c);
427     if (!GotTermCaps)
428 	GetTermCaps();
429 
430     /*
431      * Avoid a compiler bug on hpux 9.05
432      * Writing the following as func(a ? b : c) breaks
433      */
434     if (v[1])
435 	TellTC(short2str(v[1]));
436     else
437 	TellTC(defaulttell);
438 }
439 
440 /*ARGSUSED*/
441 void
442 doechotc(v, c)
443     register Char **v;
444     struct command *c;
445 {
446     if (!GotTermCaps)
447 	GetTermCaps();
448     EchoTC(++v);
449 }
450 
451 /*ARGSUSED*/
452 void
453 dosettc(v, c)
454     Char  **v;
455     struct command *c;
456 {
457     char    tv[2][BUFSIZE];
458 
459     if (!GotTermCaps)
460 	GetTermCaps();
461 
462     (void) strcpy(tv[0], short2str(v[1]));
463     (void) strcpy(tv[1], short2str(v[2]));
464     SetTC(tv[0], tv[1]);
465 }
466 
467 /* The dowhich() is by:
468  *  Andreas Luik <luik@isaak.isa.de>
469  *  I S A  GmbH - Informationssysteme fuer computerintegrierte Automatisierung
470  *  Azenberstr. 35
471  *  D-7000 Stuttgart 1
472  *  West-Germany
473  * Thanks!!
474  */
475 int
476 cmd_expand(cmd, str)
477     Char *cmd;
478     Char *str;
479 {
480     struct wordent lexp[3];
481     struct varent *vp;
482     int rv = TRUE;
483 
484     lexp[0].next = &lexp[1];
485     lexp[1].next = &lexp[2];
486     lexp[2].next = &lexp[0];
487 
488     lexp[0].prev = &lexp[2];
489     lexp[1].prev = &lexp[0];
490     lexp[2].prev = &lexp[1];
491 
492     lexp[0].word = STRNULL;
493     lexp[2].word = STRret;
494 
495     if ((vp = adrof1(cmd, &aliases)) != NULL) {
496 	if (str == NULL) {
497 	    xprintf(CGETS(22, 1, "%S: \t aliased to "), cmd);
498 	    blkpr(vp->vec);
499 	    xputchar('\n');
500 	}
501 	else
502 	    blkexpand(vp->vec, str);
503     }
504     else {
505 	lexp[1].word = cmd;
506 	rv = tellmewhat(lexp, str);
507     }
508     return rv;
509 }
510 
511 
512 /*ARGSUSED*/
513 void
514 dowhich(v, c)
515     register Char **v;
516     struct command *c;
517 {
518     int rv = TRUE;
519     USE(c);
520 
521 #ifdef notdef
522     /*
523      * We don't want to glob dowhich args because we lose quoteing
524      * E.g. which \ls if ls is aliased will not work correctly if
525      * we glob here.
526      */
527     gflag = 0, tglob(v);
528     if (gflag) {
529 	v = globall(v);
530 	if (v == 0)
531 	    stderror(ERR_NAME | ERR_NOMATCH);
532     }
533     else {
534 	v = gargv = saveblk(v);
535 	trim(v);
536     }
537 #endif
538 
539     while (*++v)
540 	rv &= cmd_expand(*v, NULL);
541 
542     if (!rv)
543 	set(STRstatus, Strsave(STR1), VAR_READWRITE);
544 
545 #ifdef notdef
546     /* Again look at the comment above; since we don't glob, we don't free */
547     if (gargv)
548 	blkfree(gargv), gargv = 0;
549 #endif
550 }
551 
552 /* PWP: a hack to start up your stopped editor on a single keystroke */
553 /* jbs - fixed hack so it worked :-) 3/28/89 */
554 
555 struct process *
556 find_stop_ed()
557 {
558     register struct process *pp, *retp;
559     register char *ep, *vp, *cp, *p;
560     int     epl, vpl, pstatus;
561 
562     if ((ep = getenv("EDITOR")) != NULL) {	/* if we have a value */
563 	if ((p = strrchr(ep, '/')) != NULL) 	/* if it has a path */
564 	    ep = p + 1;		/* then we want only the last part */
565     }
566     else
567 	ep = "ed";
568 
569     if ((vp = getenv("VISUAL")) != NULL) {	/* if we have a value */
570 	if ((p = strrchr(vp, '/')) != NULL) 	/* and it has a path */
571 	    vp = p + 1;		/* then we want only the last part */
572     }
573     else
574 	vp = "vi";
575 
576     for (vpl = 0; vp[vpl] && !Isspace(vp[vpl]); vpl++)
577 	continue;
578     for (epl = 0; ep[epl] && !Isspace(ep[epl]); epl++)
579 	continue;
580 
581     if (pcurrent == NULL)	/* see if we have any jobs */
582 	return NULL;		/* nope */
583 
584     retp = NULL;
585     for (pp = proclist.p_next; pp; pp = pp->p_next)
586 	if (pp->p_procid == pp->p_jobid) {
587 
588 	    /*
589 	     * Only foreground an edit session if it is suspended.  Some GUI
590 	     * editors have may be happily running in a separate window, no
591 	     * point in foregrounding these if they're already running - webb
592 	     */
593 	    pstatus = (int) (pp->p_flags & PALLSTATES);
594 	    if (pstatus != PINTERRUPTED && pstatus != PSTOPPED &&
595 		pstatus != PSIGNALED)
596 		continue;
597 
598 	    p = short2str(pp->p_command);
599 	    /* get the first word */
600 	    for (cp = p; *cp && !isspace((unsigned char) *cp); cp++)
601 		continue;
602 	    *cp = '\0';
603 
604 	    if ((cp = strrchr(p, '/')) != NULL)	/* and it has a path */
605 		cp = cp + 1;		/* then we want only the last part */
606 	    else
607 		cp = p;			/* else we get all of it */
608 
609 	    /* if we find either in the current name, fg it */
610 	    if (strncmp(ep, cp, (size_t) epl) == 0 ||
611 		strncmp(vp, cp, (size_t) vpl) == 0) {
612 
613 		/*
614 		 * If there is a choice, then choose the current process if
615 		 * available, or the previous process otherwise, or else
616 		 * anything will do - Robert Webb (robertw@mulga.cs.mu.oz.au).
617 		 */
618 		if (pp == pcurrent)
619 		    return pp;
620 		else if (retp == NULL || pp == pprevious)
621 		    retp = pp;
622 	    }
623 	}
624 
625     return retp;		/* Will be NULL if we didn't find a job */
626 }
627 
628 void
629 fg_proc_entry(pp)
630     register struct process *pp;
631 {
632 #ifdef BSDSIGS
633     sigmask_t omask;
634 #endif
635     jmp_buf_t osetexit;
636     bool    ohaderr;
637     Char    oGettingInput;
638 
639     getexit(osetexit);
640 
641 #ifdef BSDSIGS
642     omask = sigblock(sigmask(SIGINT));
643 #else
644     (void) sighold(SIGINT);
645 #endif
646     oGettingInput = GettingInput;
647     GettingInput = 0;
648 
649     ohaderr = haderr;		/* we need to ignore setting of haderr due to
650 				 * process getting stopped by a signal */
651     if (setexit() == 0) {	/* come back here after pjwait */
652 	pendjob();
653 	(void) alarm(0);	/* No autologout */
654 	if (!pstart(pp, 1)) {
655 	    pp->p_procid = 0;
656 	    stderror(ERR_BADJOB, pp->p_command, strerror(errno));
657 	}
658 	pjwait(pp);
659     }
660     setalarm(1);		/* Autologout back on */
661     resexit(osetexit);
662     haderr = ohaderr;
663     GettingInput = oGettingInput;
664 
665 #ifdef BSDSIGS
666     (void) sigsetmask(omask);
667 #else /* !BSDSIGS */
668     (void) sigrelse(SIGINT);
669 #endif /* BSDSIGS */
670 
671 }
672 
673 static char *
674 xgetpass(prm)
675     char *prm;
676 {
677     static char pass[PASSMAX + 1];
678     int fd, i;
679     signalfun_t sigint;
680 
681     sigint = (signalfun_t) sigset(SIGINT, SIG_IGN);
682     (void) Rawmode();	/* Make sure, cause we want echo off */
683     if ((fd = open("/dev/tty", O_RDWR)) == -1)
684 	fd = SHIN;
685 
686     xprintf("%s", prm); flush();
687     for (i = 0;;)  {
688 	if (read(fd, &pass[i], 1) < 1 || pass[i] == '\n')
689 	    break;
690 	if (i < PASSMAX)
691 	    i++;
692     }
693 
694     pass[i] = '\0';
695 
696     if (fd != SHIN)
697 	(void) close(fd);
698     (void) sigset(SIGINT, sigint);
699 
700     return(pass);
701 }
702 
703 /*
704  * Ask the user for his login password to continue working
705  * On systems that have a shadow password, this will only
706  * work for root, but what can we do?
707  *
708  * If we fail to get the password, then we log the user out
709  * immediately
710  */
711 /*ARGSUSED*/
712 static void
713 auto_lock(n)
714 	int n;
715 {
716 #ifndef NO_CRYPT
717 
718     int i;
719     char *srpp = NULL;
720     struct passwd *pw;
721 #ifdef POSIX
722     extern char *crypt __P((const char *, const char *));
723 #else
724     extern char *crypt __P(());
725 #endif
726 
727 #undef XCRYPT
728 
729 #if defined(PW_AUTH) && !defined(XCRYPT)
730 
731     struct authorization *apw;
732     extern char *crypt16 __P((const char *, const char *));
733 
734 # define XCRYPT(a, b) crypt16(a, b)
735 
736     if ((pw = getpwuid(euid)) != NULL &&	/* effective user passwd  */
737         (apw = getauthuid(euid)) != NULL) 	/* enhanced ultrix passwd */
738 	srpp = apw->a_password;
739 
740 #endif /* PW_AUTH && !XCRYPT */
741 
742 #if defined(PW_SHADOW) && !defined(XCRYPT)
743 
744     struct spwd *spw;
745 
746 # define XCRYPT(a, b) crypt(a, b)
747 
748     if ((pw = getpwuid(euid)) != NULL &&	/* effective user passwd  */
749 	(spw = getspnam(pw->pw_name)) != NULL)	/* shadowed passwd	  */
750 	srpp = spw->sp_pwdp;
751 
752 #endif /* PW_SHADOW && !XCRYPT */
753 
754 #ifndef XCRYPT
755 
756 #define XCRYPT(a, b) crypt(a, b)
757 
758     if ((pw = getpwuid(euid)) != NULL)	/* effective user passwd  */
759 	srpp = pw->pw_passwd;
760 
761 #endif /* !XCRYPT */
762 
763     if (srpp == NULL) {
764 	auto_logout(0);
765 	/*NOTREACHED*/
766 	return;
767     }
768 
769     setalarm(0);		/* Not for locking any more */
770 #ifdef BSDSIGS
771     (void) sigsetmask(sigblock(0) & ~(sigmask(SIGALRM)));
772 #else /* !BSDSIGS */
773     (void) sigrelse(SIGALRM);
774 #endif /* BSDSIGS */
775     xputchar('\n');
776     for (i = 0; i < 5; i++) {
777 	const char *crpp;
778 	char *pp;
779 #ifdef AFS
780 	char *afsname;
781 	Char *safs;
782 
783 	if ((safs = varval(STRafsuser)) != STRNULL)
784 	    afsname = short2str(safs);
785 	else
786 	    if ((afsname = getenv("AFSUSER")) == NULL)
787 	        afsname = pw->pw_name;
788 #endif
789 	pp = xgetpass("Password:");
790 
791 	crpp = XCRYPT(pp, srpp);
792 	if ((strcmp(crpp, srpp) == 0)
793 #ifdef AFS
794 	    || (ka_UserAuthenticateGeneral(KA_USERAUTH_VERSION,
795 					   afsname,     /* name */
796 					   NULL,        /* instance */
797 					   NULL,        /* realm */
798 					   pp,          /* password */
799 					   0,           /* lifetime */
800 					   0, 0,         /* spare */
801 					   NULL)        /* reason */
802 	    == 0)
803 #endif /* AFS */
804 	    ) {
805 	    (void) memset(pp, 0, PASSMAX);
806 	    if (GettingInput && !just_signaled) {
807 		(void) Rawmode();
808 		ClearLines();
809 		ClearDisp();
810 		Refresh();
811 	    }
812 	    just_signaled = 0;
813 	    return;
814 	}
815 	xprintf(CGETS(22, 2, "\nIncorrect passwd for %s\n"), pw->pw_name);
816     }
817 #endif /* NO_CRYPT */
818     auto_logout(0);
819     USE(n);
820 }
821 
822 
823 static void
824 auto_logout(n)
825     int n;
826 {
827     USE(n);
828     xprintf("auto-logout\n");
829     /* Don't leave the tty in raw mode */
830     if (editing)
831 	(void) Cookedmode();
832     (void) close(SHIN);
833     set(STRlogout, Strsave(STRautomatic), VAR_READWRITE);
834     child = 1;
835 #ifdef TESLA
836     do_logout = 1;
837 #endif /* TESLA */
838     GettingInput = FALSE; /* make flush() work to write hist files. Huber*/
839     goodbye(NULL, NULL);
840 }
841 
842 sigret_t
843 /*ARGSUSED*/
844 alrmcatch(snum)
845 int snum;
846 {
847 #ifdef UNRELSIGS
848     if (snum)
849 	(void) sigset(SIGALRM, alrmcatch);
850 #endif /* UNRELSIGS */
851 
852     (*alm_fun)(0);
853 
854     setalarm(1);
855 #ifndef SIGVOID
856     return (snum);
857 #endif /* !SIGVOID */
858 }
859 
860 /*
861  * Karl Kleinpaste, 21oct1983.
862  * Added precmd(), which checks for the alias
863  * precmd in aliases.  If it's there, the alias
864  * is executed as a command.  This is done
865  * after mailchk() and just before print-
866  * ing the prompt.  Useful for things like printing
867  * one's current directory just before each command.
868  */
869 void
870 precmd()
871 {
872 #ifdef BSDSIGS
873     sigmask_t omask;
874 
875     omask = sigblock(sigmask(SIGINT));
876 #else /* !BSDSIGS */
877     (void) sighold(SIGINT);
878 #endif /* BSDSIGS */
879     if (precmd_active) {	/* an error must have been caught */
880 	aliasrun(2, STRunalias, STRprecmd);
881 	xprintf(CGETS(22, 3, "Faulty alias 'precmd' removed.\n"));
882 	goto leave;
883     }
884     precmd_active = 1;
885     if (!whyles && adrof1(STRprecmd, &aliases))
886 	aliasrun(1, STRprecmd, NULL);
887 leave:
888     precmd_active = 0;
889 #ifdef BSDSIGS
890     (void) sigsetmask(omask);
891 #else /* !BSDSIGS */
892     (void) sigrelse(SIGINT);
893 #endif /* BSDSIGS */
894 }
895 
896 void
897 postcmd()
898 {
899 #ifdef BSDSIGS
900     sigmask_t omask;
901 
902     omask = sigblock(sigmask(SIGINT));
903 #else /* !BSDSIGS */
904     (void) sighold(SIGINT);
905 #endif /* BSDSIGS */
906     if (postcmd_active) {	/* an error must have been caught */
907 	aliasrun(2, STRunalias, STRpostcmd);
908 	xprintf(CGETS(22, 3, "Faulty alias 'postcmd' removed.\n"));
909 	goto leave;
910     }
911     postcmd_active = 1;
912     if (!whyles && adrof1(STRpostcmd, &aliases))
913 	aliasrun(1, STRpostcmd, NULL);
914 leave:
915     postcmd_active = 0;
916 #ifdef BSDSIGS
917     (void) sigsetmask(omask);
918 #else /* !BSDSIGS */
919     (void) sigrelse(SIGINT);
920 #endif /* BSDSIGS */
921 }
922 
923 /*
924  * Paul Placeway  11/24/87  Added cwd_cmd by hacking precmd() into
925  * submission...  Run every time $cwd is set (after it is set).  Useful
926  * for putting your machine and cwd (or anything else) in an xterm title
927  * space.
928  */
929 void
930 cwd_cmd()
931 {
932 #ifdef BSDSIGS
933     sigmask_t omask;
934 
935     omask = sigblock(sigmask(SIGINT));
936 #else /* !BSDSIGS */
937     (void) sighold(SIGINT);
938 #endif /* BSDSIGS */
939     if (cwdcmd_active) {	/* an error must have been caught */
940 	aliasrun(2, STRunalias, STRcwdcmd);
941 	xprintf(CGETS(22, 4, "Faulty alias 'cwdcmd' removed.\n"));
942 	goto leave;
943     }
944     cwdcmd_active = 1;
945     if (!whyles && adrof1(STRcwdcmd, &aliases))
946 	aliasrun(1, STRcwdcmd, NULL);
947 leave:
948     cwdcmd_active = 0;
949 #ifdef BSDSIGS
950     (void) sigsetmask(omask);
951 #else /* !BSDSIGS */
952     (void) sigrelse(SIGINT);
953 #endif /* BSDSIGS */
954 }
955 
956 /*
957  * Joachim Hoenig  07/16/91  Added beep_cmd, run every time tcsh wishes
958  * to beep the terminal bell. Useful for playing nice sounds instead.
959  */
960 void
961 beep_cmd()
962 {
963 #ifdef BSDSIGS
964     sigmask_t omask;
965 
966     omask = sigblock(sigmask(SIGINT));
967 #else /* !BSDSIGS */
968     (void) sighold(SIGINT);
969 #endif /* BSDSIGS */
970     if (beepcmd_active) {	/* an error must have been caught */
971 	aliasrun(2, STRunalias, STRbeepcmd);
972 	xprintf(CGETS(22, 5, "Faulty alias 'beepcmd' removed.\n"));
973     }
974     else {
975 	beepcmd_active = 1;
976 	if (!whyles && adrof1(STRbeepcmd, &aliases))
977 	    aliasrun(1, STRbeepcmd, NULL);
978     }
979     beepcmd_active = 0;
980 #ifdef BSDSIGS
981     (void) sigsetmask(omask);
982 #else /* !BSDSIGS */
983     (void) sigrelse(SIGINT);
984 #endif /* BSDSIGS */
985 }
986 
987 
988 /*
989  * Karl Kleinpaste, 18 Jan 1984.
990  * Added period_cmd(), which executes the alias "periodic" every
991  * $tperiod minutes.  Useful for occasional checking of msgs and such.
992  */
993 void
994 period_cmd()
995 {
996     register Char *vp;
997     time_t  t, interval;
998 #ifdef BSDSIGS
999     sigmask_t omask;
1000 
1001     omask = sigblock(sigmask(SIGINT));
1002 #else /* !BSDSIGS */
1003     (void) sighold(SIGINT);
1004 #endif /* BSDSIGS */
1005     if (periodic_active) {	/* an error must have been caught */
1006 	aliasrun(2, STRunalias, STRperiodic);
1007 	xprintf(CGETS(22, 6, "Faulty alias 'periodic' removed.\n"));
1008 	goto leave;
1009     }
1010     periodic_active = 1;
1011     if (!whyles && adrof1(STRperiodic, &aliases)) {
1012 	vp = varval(STRtperiod);
1013 	if (vp == STRNULL) {
1014 	    aliasrun(1, STRperiodic, NULL);
1015 	    goto leave;
1016 	}
1017 	interval = getn(vp);
1018 	(void) time(&t);
1019 	if (t - t_period >= interval * 60) {
1020 	    t_period = t;
1021 	    aliasrun(1, STRperiodic, NULL);
1022 	}
1023     }
1024 leave:
1025     periodic_active = 0;
1026 #ifdef BSDSIGS
1027     (void) sigsetmask(omask);
1028 #else /* !BSDSIGS */
1029     (void) sigrelse(SIGINT);
1030 #endif /* BSDSIGS */
1031 }
1032 
1033 /*
1034  * Karl Kleinpaste, 21oct1983.
1035  * Set up a one-word alias command, for use for special things.
1036  * This code is based on the mainline of process().
1037  */
1038 void
1039 aliasrun(cnt, s1, s2)
1040     int     cnt;
1041     Char   *s1, *s2;
1042 {
1043     struct wordent w, *new1, *new2;	/* for holding alias name */
1044     struct command *t = NULL;
1045     jmp_buf_t osetexit;
1046     int status;
1047 
1048     getexit(osetexit);
1049     if (seterr) {
1050 	xfree((ptr_t) seterr);
1051 	seterr = NULL;	/* don't repeatedly print err msg. */
1052     }
1053     w.word = STRNULL;
1054     new1 = (struct wordent *) xcalloc(1, sizeof w);
1055     new1->word = Strsave(s1);
1056     if (cnt == 1) {
1057 	/* build a lex list with one word. */
1058 	w.next = w.prev = new1;
1059 	new1->next = new1->prev = &w;
1060     }
1061     else {
1062 	/* build a lex list with two words. */
1063 	new2 = (struct wordent *) xcalloc(1, sizeof w);
1064 	new2->word = Strsave(s2);
1065 	w.next = new2->prev = new1;
1066 	new1->next = w.prev = new2;
1067 	new1->prev = new2->next = &w;
1068     }
1069 
1070     /* Save the old status */
1071     status = getn(varval(STRstatus));
1072 
1073     /* expand aliases like process() does. */
1074     alias(&w);
1075     /* build a syntax tree for the command. */
1076     t = syntax(w.next, &w, 0);
1077     if (seterr)
1078 	stderror(ERR_OLD);
1079 
1080     psavejob();
1081 
1082 
1083     /* catch any errors here */
1084     if (setexit() == 0)
1085 	/* execute the parse tree. */
1086 	/*
1087 	 * From: Michael Schroeder <mlschroe@immd4.informatik.uni-erlangen.de>
1088 	 * was execute(t, tpgrp);
1089 	 */
1090 	execute(t, tpgrp > 0 ? tpgrp : -1, NULL, NULL);
1091     /* done. free the lex list and parse tree. */
1092     freelex(&w), freesyn(t);
1093     if (haderr) {
1094 	haderr = 0;
1095 	/*
1096 	 * Either precmd, or cwdcmd, or periodic had an error. Call it again so
1097 	 * that it is removed
1098 	 */
1099 	if (precmd_active)
1100 	    precmd();
1101 	if (postcmd_active)
1102 	    postcmd();
1103 #ifdef notdef
1104 	/*
1105 	 * XXX: On the other hand, just interrupting them causes an error too.
1106 	 * So if we hit ^C in the middle of cwdcmd or periodic the alias gets
1107 	 * removed. We don't want that. Note that we want to remove precmd
1108 	 * though, cause that could lead into an infinite loop. This should be
1109 	 * fixed correctly, but then haderr should give us the whole exit
1110 	 * status not just true or false.
1111 	 */
1112 	else if (cwdcmd_active)
1113 	    cwd_cmd();
1114 	else if (beepcmd_active)
1115 	    beep_cmd();
1116 	else if (periodic_active)
1117 	    period_cmd();
1118 #endif /* notdef */
1119     }
1120     /* reset the error catcher to the old place */
1121     resexit(osetexit);
1122     prestjob();
1123     pendjob();
1124     /* Restore status */
1125     set(STRstatus, putn(status), VAR_READWRITE);
1126 }
1127 
1128 void
1129 setalarm(lck)
1130     int lck;
1131 {
1132     struct varent *vp;
1133     Char   *cp;
1134     unsigned alrm_time = 0, logout_time, lock_time;
1135     time_t cl, nl, sched_dif;
1136 
1137     if ((vp = adrof(STRautologout)) != NULL) {
1138 	if ((cp = vp->vec[0]) != 0) {
1139 	    if ((logout_time = (unsigned) atoi(short2str(cp)) * 60) > 0) {
1140 		alrm_time = logout_time;
1141 		alm_fun = auto_logout;
1142 	    }
1143 	}
1144 	if ((cp = vp->vec[1]) != 0) {
1145 	    if ((lock_time = (unsigned) atoi(short2str(cp)) * 60) > 0) {
1146 		if (lck) {
1147 		    if (alrm_time == 0 || lock_time < alrm_time) {
1148 			alrm_time = lock_time;
1149 			alm_fun = auto_lock;
1150 		    }
1151 		}
1152 		else /* lock_time always < alrm_time */
1153 		    if (alrm_time)
1154 			alrm_time -= lock_time;
1155 	    }
1156 	}
1157     }
1158     if ((nl = sched_next()) != -1) {
1159 	(void) time(&cl);
1160 	sched_dif = nl > cl ? nl - cl : 0;
1161 	if ((alrm_time == 0) || ((unsigned) sched_dif < alrm_time)) {
1162 	    alrm_time = ((unsigned) sched_dif) + 1;
1163 	    alm_fun = sched_run;
1164 	}
1165     }
1166     (void) alarm(alrm_time);	/* Autologout ON */
1167 }
1168 
1169 #undef RMDEBUG			/* For now... */
1170 
1171 void
1172 rmstar(cp)
1173     struct wordent *cp;
1174 {
1175     struct wordent *we, *args;
1176     register struct wordent *tmp, *del;
1177 
1178 #ifdef RMDEBUG
1179     static Char STRrmdebug[] = {'r', 'm', 'd', 'e', 'b', 'u', 'g', '\0'};
1180     Char   *tag;
1181 #endif /* RMDEBUG */
1182     Char   *charac;
1183     char    c;
1184     int     ask, doit, star = 0, silent = 0;
1185 
1186     if (!adrof(STRrmstar))
1187 	return;
1188 #ifdef RMDEBUG
1189     tag = varval(STRrmdebug);
1190 #endif /* RMDEBUG */
1191     we = cp->next;
1192     while (*we->word == ';' && we != cp)
1193 	we = we->next;
1194     while (we != cp) {
1195 #ifdef RMDEBUG
1196 	if (*tag)
1197 	    xprintf(CGETS(22, 7, "parsing command line\n"));
1198 #endif /* RMDEBUG */
1199 	if (!Strcmp(we->word, STRrm)) {
1200 	    args = we->next;
1201 	    ask = (*args->word != '-');
1202 	    while (*args->word == '-' && !silent) {	/* check options */
1203 		for (charac = (args->word + 1); *charac && !silent; charac++)
1204 		    silent = (*charac == 'i' || *charac == 'f');
1205 		args = args->next;
1206 	    }
1207 	    ask = (ask || (!ask && !silent));
1208 	    if (ask) {
1209 		for (; !star && *args->word != ';'
1210 		     && args != cp; args = args->next)
1211 		    if (!Strcmp(args->word, STRstar))
1212 			star = 1;
1213 		if (ask && star) {
1214 		    xprintf(CGETS(22, 8,
1215 			    "Do you really want to delete all files? [n/y] "));
1216 		    flush();
1217 		    (void) force_read(SHIN, &c, 1);
1218 		    /*
1219 		     * Perhaps we should use the yesexpr from the
1220 		     * actual locale
1221 		     */
1222 		    doit = (strchr(CGETS(22, 14, "Yy"), c) != NULL);
1223 		    while (c != '\n' && force_read(SHIN, &c, 1) == 1)
1224 			continue;
1225 		    if (!doit) {
1226 			/* remove the command instead */
1227 #ifdef RMDEBUG
1228 			if (*tag)
1229 			    xprintf(CGETS(22, 9,
1230 				    "skipping deletion of files!\n"));
1231 #endif /* RMDEBUG */
1232 			for (tmp = we;
1233 			     *tmp->word != '\n' &&
1234 			     *tmp->word != ';' && tmp != cp;) {
1235 			    tmp->prev->next = tmp->next;
1236 			    tmp->next->prev = tmp->prev;
1237 			    xfree((ptr_t) tmp->word);
1238 			    del = tmp;
1239 			    tmp = tmp->next;
1240 			    xfree((ptr_t) del);
1241 			}
1242 			if (*tmp->word == ';') {
1243 			    tmp->prev->next = tmp->next;
1244 			    tmp->next->prev = tmp->prev;
1245 			    xfree((ptr_t) tmp->word);
1246 			    del = tmp;
1247 			    xfree((ptr_t) del);
1248 			}
1249 		    }
1250 		}
1251 	    }
1252 	}
1253 	for (we = we->next;
1254 	     *we->word != ';' && we != cp;
1255 	     we = we->next)
1256 	    continue;
1257 	if (*we->word == ';')
1258 	    we = we->next;
1259     }
1260 #ifdef RMDEBUG
1261     if (*tag) {
1262 	xprintf(CGETS(22, 10, "command line now is:\n"));
1263 	for (we = cp->next; we != cp; we = we->next)
1264 	    xprintf("%S ", we->word);
1265     }
1266 #endif /* RMDEBUG */
1267     return;
1268 }
1269 
1270 #ifdef BSDJOBS
1271 /* Check if command is in continue list
1272    and do a "aliasing" if it exists as a job in background */
1273 
1274 #undef CNDEBUG			/* For now */
1275 void
1276 continue_jobs(cp)
1277     struct wordent *cp;
1278 {
1279     struct wordent *we;
1280     register struct process *pp, *np;
1281     Char   *cmd, *continue_list, *continue_args_list;
1282 
1283 #ifdef CNDEBUG
1284     Char   *tag;
1285     static Char STRcndebug[] =
1286     {'c', 'n', 'd', 'e', 'b', 'u', 'g', '\0'};
1287 #endif /* CNDEBUG */
1288     bool    in_cont_list, in_cont_arg_list;
1289 
1290 
1291 #ifdef CNDEBUG
1292     tag = varval(STRcndebug);
1293 #endif /* CNDEBUG */
1294     continue_list = varval(STRcontinue);
1295     continue_args_list = varval(STRcontinue_args);
1296     if (*continue_list == '\0' && *continue_args_list == '\0')
1297 	return;
1298 
1299     we = cp->next;
1300     while (*we->word == ';' && we != cp)
1301 	we = we->next;
1302     while (we != cp) {
1303 #ifdef CNDEBUG
1304 	if (*tag)
1305 	    xprintf(CGETS(22, 11, "parsing command line\n"));
1306 #endif /* CNDEBUG */
1307 	cmd = we->word;
1308 	in_cont_list = inlist(continue_list, cmd);
1309 	in_cont_arg_list = inlist(continue_args_list, cmd);
1310 	if (in_cont_list || in_cont_arg_list) {
1311 #ifdef CNDEBUG
1312 	    if (*tag)
1313 		xprintf(CGETS(22, 12, "in one of the lists\n"));
1314 #endif /* CNDEBUG */
1315 	    np = NULL;
1316 	    for (pp = proclist.p_next; pp; pp = pp->p_next) {
1317 		if (prefix(cmd, pp->p_command)) {
1318 		    if (pp->p_index) {
1319 			np = pp;
1320 			break;
1321 		    }
1322 		}
1323 	    }
1324 	    if (np) {
1325 		insert(we, in_cont_arg_list);
1326 	    }
1327 	}
1328 	for (we = we->next;
1329 	     *we->word != ';' && we != cp;
1330 	     we = we->next)
1331 	    continue;
1332 	if (*we->word == ';')
1333 	    we = we->next;
1334     }
1335 #ifdef CNDEBUG
1336     if (*tag) {
1337 	xprintf(CGETS(22, 13, "command line now is:\n"));
1338 	for (we = cp->next; we != cp; we = we->next)
1339 	    xprintf("%S ", we->word);
1340     }
1341 #endif /* CNDEBUG */
1342     return;
1343 }
1344 
1345 /* The actual "aliasing" of for backgrounds() is done here
1346    with the aid of insert_we().   */
1347 static void
1348 insert(pl, file_args)
1349     struct wordent *pl;
1350     bool    file_args;
1351 {
1352     struct wordent *now, *last;
1353     Char   *cmd, *bcmd, *cp1, *cp2;
1354     int     cmd_len;
1355     Char   *pause = STRunderpause;
1356     int     p_len = (int) Strlen(pause);
1357 
1358     cmd_len = (int) Strlen(pl->word);
1359     cmd = (Char *) xcalloc(1, (size_t) ((cmd_len + 1) * sizeof(Char)));
1360     (void) Strcpy(cmd, pl->word);
1361 /* Do insertions at beginning, first replace command word */
1362 
1363     if (file_args) {
1364 	now = pl;
1365 	xfree((ptr_t) now->word);
1366 	now->word = (Char *) xcalloc(1, (size_t) (5 * sizeof(Char)));
1367 	(void) Strcpy(now->word, STRecho);
1368 
1369 	now = (struct wordent *) xcalloc(1, (size_t) sizeof(struct wordent));
1370 	now->word = (Char *) xcalloc(1, (size_t) (6 * sizeof(Char)));
1371 	(void) Strcpy(now->word, STRbackqpwd);
1372 	insert_we(now, pl);
1373 
1374 	for (last = now; *last->word != '\n' && *last->word != ';';
1375 	     last = last->next)
1376 	    continue;
1377 
1378 	now = (struct wordent *) xcalloc(1, (size_t) sizeof(struct wordent));
1379 	now->word = (Char *) xcalloc(1, (size_t) (2 * sizeof(Char)));
1380 	(void) Strcpy(now->word, STRgt);
1381 	insert_we(now, last->prev);
1382 
1383 	now = (struct wordent *) xcalloc(1, (size_t) sizeof(struct wordent));
1384 	now->word = (Char *) xcalloc(1, (size_t) (2 * sizeof(Char)));
1385 	(void) Strcpy(now->word, STRbang);
1386 	insert_we(now, last->prev);
1387 
1388 	now = (struct wordent *) xcalloc(1, (size_t) sizeof(struct wordent));
1389 	now->word = (Char *) xcalloc(1, (size_t) cmd_len + p_len + 4);
1390 	cp1 = now->word;
1391 	cp2 = cmd;
1392 	*cp1++ = '~';
1393 	*cp1++ = '/';
1394 	*cp1++ = '.';
1395 	while ((*cp1++ = *cp2++) != '\0')
1396 	    continue;
1397 	cp1--;
1398 	cp2 = pause;
1399 	while ((*cp1++ = *cp2++) != '\0')
1400 	    continue;
1401 	insert_we(now, last->prev);
1402 
1403 	now = (struct wordent *) xcalloc(1, (size_t) sizeof(struct wordent));
1404 	now->word = (Char *) xcalloc(1, (size_t) (2 * sizeof(Char)));
1405 	(void) Strcpy(now->word, STRsemi);
1406 	insert_we(now, last->prev);
1407 	bcmd = (Char *) xcalloc(1, (size_t) ((cmd_len + 2) * sizeof(Char)));
1408 	cp1 = bcmd;
1409 	cp2 = cmd;
1410 	*cp1++ = '%';
1411 	while ((*cp1++ = *cp2++) != '\0')
1412 	    continue;
1413 	now = (struct wordent *) xcalloc(1, (size_t) (sizeof(struct wordent)));
1414 	now->word = bcmd;
1415 	insert_we(now, last->prev);
1416     }
1417     else {
1418 	struct wordent *del;
1419 
1420 	now = pl;
1421 	xfree((ptr_t) now->word);
1422 	now->word = (Char *) xcalloc(1,
1423 				     (size_t) ((cmd_len + 2) * sizeof(Char)));
1424 	cp1 = now->word;
1425 	cp2 = cmd;
1426 	*cp1++ = '%';
1427 	while ((*cp1++ = *cp2++) != '\0')
1428 	    continue;
1429 	for (now = now->next;
1430 	     *now->word != '\n' && *now->word != ';' && now != pl;) {
1431 	    now->prev->next = now->next;
1432 	    now->next->prev = now->prev;
1433 	    xfree((ptr_t) now->word);
1434 	    del = now;
1435 	    now = now->next;
1436 	    xfree((ptr_t) del);
1437 	}
1438     }
1439 }
1440 
1441 static void
1442 insert_we(new, where)
1443     struct wordent *new, *where;
1444 {
1445 
1446     new->prev = where;
1447     new->next = where->next;
1448     where->next = new;
1449     new->next->prev = new;
1450 }
1451 
1452 static int
1453 inlist(list, name)
1454     Char   *list, *name;
1455 {
1456     register Char *l, *n;
1457 
1458     l = list;
1459     n = name;
1460 
1461     while (*l && *n) {
1462 	if (*l == *n) {
1463 	    l++;
1464 	    n++;
1465 	    if (*n == '\0' && (*l == ' ' || *l == '\0'))
1466 		return (1);
1467 	    else
1468 		continue;
1469 	}
1470 	else {
1471 	    while (*l && *l != ' ')
1472 		l++;		/* skip to blank */
1473 	    while (*l && *l == ' ')
1474 		l++;		/* and find first nonblank character */
1475 	    n = name;
1476 	}
1477     }
1478     return (0);
1479 }
1480 
1481 #endif /* BSDJOBS */
1482 
1483 
1484 /*
1485  * Implement a small cache for tilde names. This is used primarily
1486  * to expand tilde names to directories, but also
1487  * we can find users from their home directories for the tilde
1488  * prompt, on machines where yp lookup is slow this can be a big win...
1489  * As with any cache this can run out of sync, rehash can sync it again.
1490  */
1491 static struct tildecache {
1492     Char   *user;
1493     Char   *home;
1494     int     hlen;
1495 }      *tcache = NULL;
1496 
1497 #define TILINCR 10
1498 int tlength = 0;
1499 static int tsize = TILINCR;
1500 
1501 static int
1502 tildecompare(p1, p2)
1503     struct tildecache *p1, *p2;
1504 {
1505     return Strcmp(p1->user, p2->user);
1506 }
1507 
1508 static Char *
1509 gethomedir(us)
1510     Char   *us;
1511 {
1512     register struct passwd *pp;
1513 #ifdef HESIOD
1514     char **res, **res1, *cp;
1515     Char *rp;
1516 #endif /* HESIOD */
1517 
1518     pp = getpwnam(short2str(us));
1519 #ifdef YPBUGS
1520     fix_yp_bugs();
1521 #endif /* YPBUGS */
1522     if (pp != NULL)
1523 	return Strsave(str2short(pp->pw_dir));
1524 #ifdef HESIOD
1525     res = hes_resolve(short2str(us), "filsys");
1526     rp = 0;
1527     if (res != 0) {
1528 	extern char *strtok();
1529 	if ((*res) != 0) {
1530 	    /*
1531 	     * Look at the first token to determine how to interpret
1532 	     * the rest of it.
1533 	     * Yes, strtok is evil (it's not thread-safe), but it's also
1534 	     * easy to use.
1535 	     */
1536 	    cp = strtok(*res, " ");
1537 	    if (strcmp(cp, "AFS") == 0) {
1538 		/* next token is AFS pathname.. */
1539 		cp = strtok(NULL, " ");
1540 		if (cp != NULL)
1541 		    rp = Strsave(str2short(cp));
1542 	    } else if (strcmp(cp, "NFS") == 0) {
1543 		cp = NULL;
1544 		if ((strtok(NULL, " ")) && /* skip remote pathname */
1545 		    (strtok(NULL, " ")) && /* skip host */
1546 		    (strtok(NULL, " ")) && /* skip mode */
1547 		    (cp = strtok(NULL, " "))) {
1548 		    rp = Strsave(str2short(cp));
1549 		}
1550 	    }
1551 	}
1552 	for (res1 = res; *res1; res1++)
1553 	    free(*res1);
1554 	return rp;
1555     }
1556 #endif /* HESIOD */
1557     return NULL;
1558 }
1559 
1560 Char   *
1561 gettilde(us)
1562     Char   *us;
1563 {
1564     struct tildecache *bp1, *bp2, *bp;
1565     Char *hd;
1566 
1567     /* Ignore NIS special names */
1568     if (*us == '+' || *us == '-')
1569 	return NULL;
1570 
1571     if (tcache == NULL)
1572 	tcache = (struct tildecache *) xmalloc((size_t) (TILINCR *
1573 						  sizeof(struct tildecache)));
1574     /*
1575      * Binary search
1576      */
1577     for (bp1 = tcache, bp2 = tcache + tlength; bp1 < bp2;) {
1578 	register int i;
1579 
1580 	bp = bp1 + ((bp2 - bp1) >> 1);
1581 	if ((i = *us - *bp->user) == 0 && (i = Strcmp(us, bp->user)) == 0)
1582 	    return (bp->home);
1583 	if (i < 0)
1584 	    bp2 = bp;
1585 	else
1586 	    bp1 = bp + 1;
1587     }
1588     /*
1589      * Not in the cache, try to get it from the passwd file
1590      */
1591     hd = gethomedir(us);
1592     if (hd == NULL)
1593 	return NULL;
1594 
1595     /*
1596      * Update the cache
1597      */
1598     tcache[tlength].user = Strsave(us);
1599     tcache[tlength].home = hd;
1600     tcache[tlength++].hlen = (int) Strlen(hd);
1601 
1602     qsort((ptr_t) tcache, (size_t) tlength, sizeof(struct tildecache),
1603 	  (int (*) __P((const void *, const void *))) tildecompare);
1604 
1605     if (tlength == tsize) {
1606 	tsize += TILINCR;
1607 	tcache = (struct tildecache *) xrealloc((ptr_t) tcache,
1608 						(size_t) (tsize *
1609 						  sizeof(struct tildecache)));
1610     }
1611     return (hd);
1612 }
1613 
1614 /*
1615  * Return the username if the directory path passed contains a
1616  * user's home directory in the tilde cache, otherwise return NULL
1617  * hm points to the place where the path became different.
1618  * Special case: Our own home directory.
1619  * If we are passed a null pointer, then we flush the cache.
1620  */
1621 Char   *
1622 getusername(hm)
1623     Char  **hm;
1624 {
1625     Char   *h, *p;
1626     int     i, j;
1627 
1628     if (hm == NULL) {
1629 	for (i = 0; i < tlength; i++) {
1630 	    xfree((ptr_t) tcache[i].home);
1631 	    xfree((ptr_t) tcache[i].user);
1632 	}
1633 	xfree((ptr_t) tcache);
1634 	tlength = 0;
1635 	tsize = TILINCR;
1636 	tcache = NULL;
1637 	return NULL;
1638     }
1639     if (((h = varval(STRhome)) != STRNULL) &&
1640 	(Strncmp(p = *hm, h, (size_t) (j = (int) Strlen(h))) == 0) &&
1641 	(p[j] == '/' || p[j] == '\0')) {
1642 	*hm = &p[j];
1643 	return STRNULL;
1644     }
1645     for (i = 0; i < tlength; i++)
1646 	if ((Strncmp(p = *hm, tcache[i].home, (size_t)
1647 	    (j = tcache[i].hlen)) == 0) && (p[j] == '/' || p[j] == '\0')) {
1648 	    *hm = &p[j];
1649 	    return tcache[i].user;
1650 	}
1651     return NULL;
1652 }
1653 
1654 #ifdef OBSOLETE
1655 /*
1656  * PWP: read a bunch of aliases out of a file QUICKLY.  The format
1657  *  is almost the same as the result of saying "alias > FILE", except
1658  *  that saying "aliases > FILE" does not expand non-letters to printable
1659  *  sequences.
1660  */
1661 /*ARGSUSED*/
1662 void
1663 doaliases(v, c)
1664     Char  **v;
1665     struct command *c;
1666 {
1667     jmp_buf_t oldexit;
1668     Char  **vec, *lp;
1669     int     fd;
1670     Char    buf[BUFSIZE], line[BUFSIZE];
1671     char    tbuf[BUFSIZE + 1], *tmp;
1672     extern bool output_raw;	/* PWP: in sh.print.c */
1673 
1674     USE(c);
1675     v++;
1676     if (*v == 0) {
1677 	output_raw = 1;
1678 	plist(&aliases, VAR_ALL);
1679 	output_raw = 0;
1680 	return;
1681     }
1682 
1683     gflag = 0, tglob(v);
1684     if (gflag) {
1685 	v = globall(v);
1686 	if (v == 0)
1687 	    stderror(ERR_NAME | ERR_NOMATCH);
1688     }
1689     else {
1690 	v = gargv = saveblk(v);
1691 	trim(v);
1692     }
1693 
1694     if ((fd = open(tmp = short2str(*v), O_RDONLY)) < 0)
1695 	stderror(ERR_NAME | ERR_SYSTEM, tmp, strerror(errno));
1696 
1697     getexit(oldexit);
1698     if (setexit() == 0) {
1699 	for (;;) {
1700 	    Char   *p = NULL;
1701 	    int     n = 0;
1702 	    lp = line;
1703 	    for (;;) {
1704 		if (n <= 0) {
1705 		    int     i;
1706 
1707 		    if ((n = read(fd, tbuf, BUFSIZE)) <= 0) {
1708 #ifdef convex
1709 			stderror(ERR_SYSTEM, progname, strerror(errno));
1710 #endif /* convex */
1711 			goto eof;
1712 		    }
1713 		    for (i = 0; i < n; i++)
1714   			buf[i] = (Char) tbuf[i];
1715 		    p = buf;
1716 		}
1717 		n--;
1718 		if ((*lp++ = *p++) == '\n') {
1719 		    lp[-1] = '\0';
1720 		    break;
1721 		}
1722 	    }
1723 	    for (lp = line; *lp; lp++) {
1724 		if (isspc(*lp)) {
1725 		    *lp++ = '\0';
1726 		    while (isspc(*lp))
1727 			lp++;
1728 		    vec = (Char **) xmalloc((size_t)
1729 					    (2 * sizeof(Char **)));
1730 		    vec[0] = Strsave(lp);
1731 		    vec[1] = NULL;
1732 		    setq(strip(line), vec, &aliases, VAR_READWRITE);
1733 		    break;
1734 		}
1735 	    }
1736 	}
1737     }
1738 
1739 eof:
1740     (void) close(fd);
1741     tw_cmd_free();
1742     if (gargv)
1743 	blkfree(gargv), gargv = 0;
1744     resexit(oldexit);
1745 }
1746 #endif /* OBSOLETE */
1747 
1748 
1749 /*
1750  * set the shell-level var to 1 or apply change to it.
1751  */
1752 void
1753 shlvl(val)
1754     int val;
1755 {
1756     char *cp;
1757 
1758     if ((cp = getenv("SHLVL")) != NULL) {
1759 
1760 	if (loginsh)
1761 	    val = 1;
1762 	else
1763 	    val += atoi(cp);
1764 
1765 	if (val <= 0) {
1766 	    if (adrof(STRshlvl) != NULL)
1767 		unsetv(STRshlvl);
1768 	    Unsetenv(STRKSHLVL);
1769 	}
1770 	else {
1771 	    Char    buff[BUFSIZE];
1772 
1773 	    (void) Itoa(val, buff, 0, 0);
1774 	    set(STRshlvl, Strsave(buff), VAR_READWRITE);
1775 	    tsetenv(STRKSHLVL, buff);
1776 	}
1777     }
1778     else {
1779 	set(STRshlvl, SAVE("1"), VAR_READWRITE);
1780 	tsetenv(STRKSHLVL, str2short("1"));
1781     }
1782 }
1783 
1784 
1785 /* fixio():
1786  *	Try to recover from a read error
1787  */
1788 int
1789 fixio(fd, e)
1790     int fd, e;
1791 {
1792     switch (e) {
1793     case -1:	/* Make sure that the code is reachable */
1794 
1795 #ifdef EWOULDBLOCK
1796     case EWOULDBLOCK:
1797 # define FDRETRY
1798 #endif /* EWOULDBLOCK */
1799 
1800 #if defined(POSIX) && defined(EAGAIN)
1801 # if !defined(EWOULDBLOCK) || EWOULDBLOCK != EAGAIN
1802     case EAGAIN:
1803 #  define FDRETRY
1804 # endif /* !EWOULDBLOCK || EWOULDBLOCK != EAGAIN */
1805 #endif /* POSIX && EAGAIN */
1806 
1807 	e = 0;
1808 #ifdef FDRETRY
1809 # ifdef F_SETFL
1810 /*
1811  * Great! we have on suns 3 flavors and 5 names...
1812  * I hope that will cover everything.
1813  * I added some more defines... many systems have different defines.
1814  * Rather than dealing with getting the right includes, we'll just
1815  * cover all the known possibilities here.  -- sterling@netcom.com
1816  */
1817 #  ifndef O_NONBLOCK
1818 #   define O_NONBLOCK 0
1819 #  endif /* O_NONBLOCK */
1820 #  ifndef O_NDELAY
1821 #   define O_NDELAY 0
1822 #  endif /* O_NDELAY */
1823 #  ifndef FNBIO
1824 #   define FNBIO 0
1825 #  endif /* FNBIO */
1826 #  ifndef _FNBIO
1827 #   define _FNBIO 0
1828 #  endif /* _FNBIO */
1829 #  ifndef FNONBIO
1830 #   define FNONBIO 0
1831 #  endif /* FNONBIO */
1832 #  ifndef FNONBLOCK
1833 #   define FNONBLOCK 0
1834 #  endif /* FNONBLOCK */
1835 #  ifndef _FNONBLOCK
1836 #   define _FNONBLOCK 0
1837 #  endif /* _FNONBLOCK */
1838 #  ifndef FNDELAY
1839 #   define FNDELAY 0
1840 #  endif /* FNDELAY */
1841 #  ifndef _FNDELAY
1842 #   define _FNDELAY 0
1843 #  endif /* _FNDELAY */
1844 #  ifndef FNDLEAY	/* Some linux versions have this typo */
1845 #   define FNDLEAY 0
1846 #  endif /* FNDLEAY */
1847 	if ((e = fcntl(fd, F_GETFL, 0)) == -1)
1848 	    return -1;
1849 
1850 	e &= ~(O_NDELAY|O_NONBLOCK|FNBIO|_FNBIO|FNONBIO|FNONBLOCK|_FNONBLOCK|
1851 	       FNDELAY|_FNDELAY|FNDLEAY);	/* whew! */
1852 	if (fcntl(fd, F_SETFL, e) == -1)
1853 	    return -1;
1854 	else
1855 	    e = 1;
1856 # endif /* F_SETFL */
1857 
1858 # ifdef FIONBIO
1859 	e = 0;
1860 	if (ioctl(fd, FIONBIO, (ioctl_t) &e) == -1)
1861 	    return -1;
1862 	else
1863 	    e = 1;
1864 # endif	/* FIONBIO */
1865 
1866 #endif /* FDRETRY */
1867 	return e ? 0 : -1;
1868 
1869     case EINTR:
1870 	return 0;
1871 
1872     default:
1873 	return -1;
1874     }
1875 }
1876 
1877 /* collate():
1878  *	String collation
1879  */
1880 int
1881 collate(a, b)
1882     const Char *a;
1883     const Char *b;
1884 {
1885     int rv;
1886 #ifdef SHORT_STRINGS
1887     /* This strips the quote bit as a side effect */
1888     char *sa = strsave(short2str(a));
1889     char *sb = strsave(short2str(b));
1890 #else
1891     char *sa = strip(strsave(a));
1892     char *sb = strip(strsave(b));
1893 #endif /* SHORT_STRINGS */
1894 
1895 #if defined(NLS) && !defined(NOSTRCOLL)
1896     errno = 0;	/* strcoll sets errno, another brain-damage */
1897 
1898     rv = strcoll(sa, sb);
1899 
1900     /*
1901      * We should be checking for errno != 0, but some systems
1902      * forget to reset errno to 0. So we only check for the
1903      * only documented valid errno value for strcoll [EINVAL]
1904      */
1905     if (errno == EINVAL) {
1906 	xfree((ptr_t) sa);
1907 	xfree((ptr_t) sb);
1908 	stderror(ERR_SYSTEM, "strcoll", strerror(errno));
1909     }
1910 #else
1911     rv = strcmp(sa, sb);
1912 #endif /* NLS && !NOSTRCOLL */
1913 
1914     xfree((ptr_t) sa);
1915     xfree((ptr_t) sb);
1916 
1917     return rv;
1918 }
1919 
1920 #ifdef HASHBANG
1921 /*
1922  * From: peter@zeus.dialix.oz.au (Peter Wemm)
1923  * If exec() fails look first for a #! [word] [word] ....
1924  * If it is, splice the header into the argument list and retry.
1925  */
1926 #define HACKBUFSZ 1024		/* Max chars in #! vector */
1927 #define HACKVECSZ 128		/* Max words in #! vector */
1928 int
1929 hashbang(fd, vp)
1930     int fd;
1931     Char ***vp;
1932 {
1933     unsigned char lbuf[HACKBUFSZ];
1934     char *sargv[HACKVECSZ];
1935     unsigned char *p, *ws;
1936     int sargc = 0;
1937 #ifdef WINNT
1938     int fw = 0; 	/* found at least one word */
1939     int first_word = 0;
1940 #endif /* WINNT */
1941 
1942     if (read(fd, (char *) lbuf, HACKBUFSZ) <= 0)
1943 	return -1;
1944 
1945     ws = 0;	/* word started = 0 */
1946 
1947     for (p = lbuf; p < &lbuf[HACKBUFSZ]; )
1948 	switch (*p) {
1949 	case ' ':
1950 	case '\t':
1951 #ifdef WINNT
1952 	case '\r':
1953 #endif /* WINNT */
1954 	    if (ws) {	/* a blank after a word.. save it */
1955 		*p = '\0';
1956 #ifndef WINNT
1957 		if (sargc < HACKVECSZ - 1)
1958 		    sargv[sargc++] = ws;
1959 		ws = NULL;
1960 #else /* WINNT */
1961 		if (sargc < HACKVECSZ - 1) {
1962 		    sargv[sargc] = first_word ? NULL: hb_subst(ws);
1963 		    if (sargv[sargc] == NULL)
1964 			sargv[sargc] = ws;
1965 		    sargc++;
1966 		}
1967 		ws = NULL;
1968 	    	fw = 1;
1969 		first_word = 1;
1970 #endif /* WINNT */
1971 	    }
1972 	    p++;
1973 	    continue;
1974 
1975 	case '\0':	/* Whoa!! what the hell happened */
1976 	    return -1;
1977 
1978 	case '\n':	/* The end of the line. */
1979 	    if (
1980 #ifdef WINNT
1981 		fw ||
1982 #endif /* WINNT */
1983 		ws) {	/* terminate the last word */
1984 		*p = '\0';
1985 #ifndef WINNT
1986 		if (sargc < HACKVECSZ - 1)
1987 		    sargv[sargc++] = ws;
1988 #else /* WINNT */
1989 		if (sargc < HACKVECSZ - 1) { /* deal with the 1-word case */
1990 		    sargv[sargc] = first_word? NULL : hb_subst(ws);
1991 		    if (sargv[sargc] == NULL)
1992 			sargv[sargc] = ws;
1993 		    sargc++;
1994 		}
1995 #endif /* !WINNT */
1996 	    }
1997 	    sargv[sargc] = NULL;
1998 	    ws = NULL;
1999 	    if (sargc > 0) {
2000 		*vp = blk2short(sargv);
2001 		return 0;
2002 	    }
2003 	    else
2004 		return -1;
2005 
2006 	default:
2007 	    if (!ws)	/* Start a new word? */
2008 		ws = p;
2009 	    p++;
2010 	    break;
2011 	}
2012     return -1;
2013 }
2014 #endif /* HASHBANG */
2015 
2016 #ifdef REMOTEHOST
2017 
2018 static sigret_t
2019 palarm(snum)
2020     int snum;
2021 {
2022     USE(snum);
2023 #ifdef UNRELSIGS
2024     if (snum)
2025 	(void) sigset(snum, SIG_IGN);
2026 #endif /* UNRELSIGS */
2027     (void) alarm(0);
2028     reset();
2029 
2030 #ifndef SIGVOID
2031     return (snum);
2032 #endif
2033 }
2034 
2035 
2036 static void
2037 getremotehost()
2038 {
2039     const char *host = NULL;
2040     struct hostent* hp;
2041     struct sockaddr_in saddr;
2042     int len = sizeof(struct sockaddr_in);
2043 #if defined(UTHOST) && !defined(HAVENOUTMP)
2044     char *sptr = NULL;
2045 #endif
2046 
2047     if (getpeername(SHIN, (struct sockaddr *) &saddr, &len) != -1) {
2048 #if 0
2049 	if ((hp = gethostbyaddr((char *)&saddr.sin_addr, sizeof(struct in_addr),
2050 				AF_INET)) != NULL)
2051 	    host = hp->h_name;
2052 	else
2053 #endif
2054 	    host = inet_ntoa(saddr.sin_addr);
2055     }
2056 #if defined(UTHOST) && !defined(HAVENOUTMP)
2057     else {
2058 	char *ptr;
2059 	char *name = utmphost();
2060 	/* Avoid empty names and local X displays */
2061 	if (name != NULL && *name != '\0' && *name != ':') {
2062 	    /* Look for host:display.screen */
2063 	    if ((sptr = strchr(name, ':')) != NULL)
2064 		*sptr = '\0';
2065 	    /* Leave IP address as is */
2066 	    if (isdigit(*name))
2067 		host = name;
2068 	    else {
2069 		if (sptr != name) {
2070 		    if ((hp = gethostbyname(name)) == NULL) {
2071 			/* Try again eliminating the trailing domain */
2072 			if ((ptr = strchr(name, '.')) != NULL) {
2073 			    *ptr = '\0';
2074 			    if ((hp = gethostbyname(name)) != NULL)
2075 				host = hp->h_name;
2076 			    *ptr = '.';
2077 			}
2078 		    }
2079 		    else
2080 			host = hp->h_name;
2081 		}
2082 	    }
2083 	}
2084     }
2085 #endif
2086 
2087     if (host)
2088 	tsetenv(STRREMOTEHOST, str2short(host));
2089 
2090 #if defined(UTHOST) && !defined(HAVENOUTMP)
2091     if (sptr)
2092 	*sptr = ':';
2093 #endif
2094 }
2095 
2096 
2097 /*
2098  * From: <lesv@ppvku.ericsson.se> (Lennart Svensson)
2099  */
2100 void
2101 remotehost()
2102 {
2103     /* Don't get stuck if the resolver does not work! */
2104     signalfun_t osig = sigset(SIGALRM, palarm);
2105 
2106     jmp_buf_t osetexit;
2107     getexit(osetexit);
2108 
2109     (void) alarm(2);
2110 
2111     if (setexit() == 0)
2112 	getremotehost();
2113 
2114     resexit(osetexit);
2115 
2116     (void) alarm(0);
2117     (void) sigset(SIGALRM, osig);
2118 
2119 #ifdef YPBUGS
2120     /* From: casper@fwi.uva.nl (Casper H.S. Dik), for Solaris 2.3 */
2121     fix_yp_bugs();
2122 #endif /* YPBUGS */
2123 
2124 }
2125 #endif /* REMOTEHOST */
2126