xref: /freebsd/contrib/tcsh/tc.prompt.c (revision 2b743a9e9ddc6736208dc8ca1ce06ce64ad20a19)
1 /* $Header: /src/pub/tcsh/tc.prompt.c,v 3.53 2005/01/05 18:06:43 christos Exp $ */
2 /*
3  * tc.prompt.c: Prompt printing stuff
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. Neither the name of the University nor the names of its contributors
18  *    may be used to endorse or promote products derived from this software
19  *    without specific prior written permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
22  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
25  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31  * SUCH DAMAGE.
32  */
33 #include "sh.h"
34 
35 RCSID("$Id: tc.prompt.c,v 3.53 2005/01/05 18:06:43 christos Exp $")
36 
37 #include "ed.h"
38 #include "tw.h"
39 
40 /*
41  * kfk 21oct1983 -- add @ (time) and / ($cwd) in prompt.
42  * PWP 4/27/87 -- rearange for tcsh.
43  * mrdch@com.tau.edu.il 6/26/89 - added ~, T and .# - rearanged to switch()
44  *                 instead of if/elseif
45  * Luke Mewburn, <lukem@cs.rmit.edu.au>
46  *	6-Sep-91	changed date format
47  *	16-Feb-94	rewrote directory prompt code, added $ellipsis
48  *	29-Dec-96	added rprompt support
49  */
50 
51 static const char   *month_list[12];
52 static const char   *day_list[7];
53 
54 void
55 dateinit()
56 {
57 #ifdef notyet
58   int i;
59 
60   setlocale(LC_TIME, "");
61 
62   for (i = 0; i < 12; i++)
63       xfree((ptr_t) month_list[i]);
64   month_list[0] = strsave(_time_info->abbrev_month[0]);
65   month_list[1] = strsave(_time_info->abbrev_month[1]);
66   month_list[2] = strsave(_time_info->abbrev_month[2]);
67   month_list[3] = strsave(_time_info->abbrev_month[3]);
68   month_list[4] = strsave(_time_info->abbrev_month[4]);
69   month_list[5] = strsave(_time_info->abbrev_month[5]);
70   month_list[6] = strsave(_time_info->abbrev_month[6]);
71   month_list[7] = strsave(_time_info->abbrev_month[7]);
72   month_list[8] = strsave(_time_info->abbrev_month[8]);
73   month_list[9] = strsave(_time_info->abbrev_month[9]);
74   month_list[10] = strsave(_time_info->abbrev_month[10]);
75   month_list[11] = strsave(_time_info->abbrev_month[11]);
76 
77   for (i = 0; i < 7; i++)
78       xfree((ptr_t) day_list[i]);
79   day_list[0] = strsave(_time_info->abbrev_wkday[0]);
80   day_list[1] = strsave(_time_info->abbrev_wkday[1]);
81   day_list[2] = strsave(_time_info->abbrev_wkday[2]);
82   day_list[3] = strsave(_time_info->abbrev_wkday[3]);
83   day_list[4] = strsave(_time_info->abbrev_wkday[4]);
84   day_list[5] = strsave(_time_info->abbrev_wkday[5]);
85   day_list[6] = strsave(_time_info->abbrev_wkday[6]);
86 #else
87   month_list[0] = "Jan";
88   month_list[1] = "Feb";
89   month_list[2] = "Mar";
90   month_list[3] = "Apr";
91   month_list[4] = "May";
92   month_list[5] = "Jun";
93   month_list[6] = "Jul";
94   month_list[7] = "Aug";
95   month_list[8] = "Sep";
96   month_list[9] = "Oct";
97   month_list[10] = "Nov";
98   month_list[11] = "Dec";
99 
100   day_list[0] = "Sun";
101   day_list[1] = "Mon";
102   day_list[2] = "Tue";
103   day_list[3] = "Wed";
104   day_list[4] = "Thu";
105   day_list[5] = "Fri";
106   day_list[6] = "Sat";
107 #endif
108 }
109 
110 void
111 printprompt(promptno, str)
112     int     promptno;
113     const char   *str;
114 {
115     static  Char *ocp = NULL;
116     static  const char *ostr = NULL;
117     time_t  lclock = time(NULL);
118     Char   *cp;
119 
120     switch (promptno) {
121     default:
122     case 0:
123 	cp = varval(STRprompt);
124 	break;
125     case 1:
126 	cp = varval(STRprompt2);
127 	break;
128     case 2:
129 	cp = varval(STRprompt3);
130 	break;
131     case 3:
132 	if (ocp != NULL) {
133 	    cp = ocp;
134 	    str = ostr;
135 	}
136 	else
137 	    cp = varval(STRprompt);
138 	break;
139     }
140 
141     if (promptno < 2) {
142 	ocp = cp;
143 	ostr = str;
144     }
145 
146     PromptBuf[0] = '\0';
147     tprintf(FMT_PROMPT, PromptBuf, cp, 2 * INBUFSIZE - 2, str, lclock, NULL);
148     if (!editing) {
149 	for (cp = PromptBuf; *cp ; )
150 	    (void) putwraw(*cp++);
151 	SetAttributes(0);
152 	flush();
153     }
154 
155     RPromptBuf[0] = '\0';
156     if (promptno == 0) {	/* determine rprompt if using main prompt */
157 	cp = varval(STRrprompt);
158 	tprintf(FMT_PROMPT, RPromptBuf, cp, INBUFSIZE - 2, NULL, lclock, NULL);
159 				/* if not editing, put rprompt after prompt */
160 	if (!editing && RPromptBuf[0] != '\0') {
161 	    for (cp = RPromptBuf; *cp ; )
162 		(void) putwraw(*cp++);
163 	    SetAttributes(0);
164 	    putraw(' ');
165 	    flush();
166 	}
167     }
168 }
169 
170 void
171 tprintf(what, buf, fmt, siz, str, tim, info)
172     int what;
173     Char *buf;
174     const Char *fmt;
175     size_t siz;
176     const char *str;
177     time_t tim;
178     ptr_t info;
179 {
180     Char   *z, *q;
181     Char    attributes = 0;
182     static int print_prompt_did_ding = 0;
183     Char    buff[BUFSIZE];
184     /* Need to be unsigned to avoid sign extension */
185     const unsigned char   *cz;
186     unsigned char    cbuff[BUFSIZE];
187 
188     Char *p  = buf;
189     Char *ep = &p[siz];
190     const Char *cp = fmt;
191     Char Scp;
192     struct tm *t = localtime(&tim);
193 
194 			/* prompt stuff */
195     static Char *olddir = NULL, *olduser = NULL;
196     int updirs;
197     size_t pdirs, sz;
198     int l;
199 
200     for (; *cp; cp++) {
201 	if (p >= ep)
202 	    break;
203 	l = NLSSize(cp, -1);
204 	if (l > 1) {
205 	    while (l--)
206 		*p++ = attributes | *cp++;
207 	    cp--;
208 	    continue;
209 	}
210 	if ((*cp == '%') && ! (cp[1] == '\0')) {
211 	    cp++;
212 	    switch (*cp) {
213 	    case 'R':
214 		if (what == FMT_HISTORY) {
215 		    fmthist('R', info, (char *) cbuff, sizeof(cbuff));
216 		    cz = cbuff;
217 		} else
218 		    cz = (const unsigned char *) str;
219 		if (cz != NULL)
220 		    for (; *cz && p < ep; p++) {
221 			cz += one_mbtowc(p, (const char *)cz, MB_LEN_MAX);
222 			*p |= attributes;
223 		    }
224 		break;
225 	    case '#':
226 		*p++ = attributes | ((uid == 0) ? PRCHROOT : PRCH);
227 		break;
228 	    case '!':
229 	    case 'h':
230 		switch (what) {
231 		case FMT_HISTORY:
232 		    fmthist('h', info, (char *) cbuff, sizeof(cbuff));
233 		    break;
234 		case FMT_SCHED:
235 		    (void) xsnprintf((char *) cbuff, sizeof(cbuff), "%d",
236 			*(int *)info);
237 		    break;
238 		default:
239 		    (void) xsnprintf((char *) cbuff, sizeof(cbuff), "%d",
240 			eventno + 1);
241 		    break;
242 		}
243 		for (cz = cbuff; *cz && p < ep; p++) {
244 		    cz += one_mbtowc(p, (const char *)cz, MB_LEN_MAX);
245 		    *p |= attributes;
246 		}
247 		break;
248 	    case 'T':		/* 24 hour format	 */
249 	    case '@':
250 	    case 't':		/* 12 hour am/pm format */
251 	    case 'p':		/* With seconds	*/
252 	    case 'P':
253 		{
254 		    char    ampm = 'a';
255 		    int     hr = t->tm_hour;
256 
257 		    if (p >= ep - 10) break;
258 
259 		    /* addition by Hans J. Albertsson */
260 		    /* and another adapted from Justin Bur */
261 		    if (adrof(STRampm) || (*cp != 'T' && *cp != 'P')) {
262 			if (hr >= 12) {
263 			    if (hr > 12)
264 				hr -= 12;
265 			    ampm = 'p';
266 			}
267 			else if (hr == 0)
268 			    hr = 12;
269 		    }		/* else do a 24 hour clock */
270 
271 		    /* "DING!" stuff by Hans also */
272 		    if (t->tm_min || print_prompt_did_ding ||
273 			what != FMT_PROMPT || adrof(STRnoding)) {
274 			if (t->tm_min)
275 			    print_prompt_did_ding = 0;
276 			p = Itoa(hr, p, 0, attributes);
277 			*p++ = attributes | ':';
278 			p = Itoa(t->tm_min, p, 2, attributes);
279 			if (*cp == 'p' || *cp == 'P') {
280 			    *p++ = attributes | ':';
281 			    p = Itoa(t->tm_sec, p, 2, attributes);
282 			}
283 			if (adrof(STRampm) || (*cp != 'T' && *cp != 'P')) {
284 			    *p++ = attributes | ampm;
285 			    *p++ = attributes | 'm';
286 			}
287 		    }
288 		    else {	/* we need to ding */
289 			int     i = 0;
290 
291 			(void) Strcpy(buff, STRDING);
292 			while (buff[i]) {
293 			    *p++ = attributes | buff[i++];
294 			}
295 			print_prompt_did_ding = 1;
296 		    }
297 		}
298 		break;
299 
300 	    case 'M':
301 #ifndef HAVENOUTMP
302 		if (what == FMT_WHO)
303 		    cz = (const unsigned char *) who_info(info, 'M',
304 			(char *) cbuff, sizeof(cbuff));
305 		else
306 #endif /* HAVENOUTMP */
307 		    cz = (const unsigned char *) getenv("HOST");
308 		/*
309 		 * Bug pointed out by Laurent Dami <dami@cui.unige.ch>: don't
310 		 * derefrence that NULL (if HOST is not set)...
311 		 */
312 		if (cz != NULL)
313 		    for (; *cz && p < ep; p++) {
314 			cz += one_mbtowc(p, (const char *)cz, MB_LEN_MAX);
315 			*p |= attributes;
316 		    }
317 		break;
318 
319 	    case 'm':
320 #ifndef HAVENOUTMP
321 		if (what == FMT_WHO)
322 		    cz = (const unsigned char *) who_info(info, 'm',
323 			(char *) cbuff, sizeof(cbuff));
324 		else
325 #endif /* HAVENOUTMP */
326 		    cz = (const unsigned char *) getenv("HOST");
327 
328 		if (cz != NULL)
329 		    for (; *cz && (what == FMT_WHO || *cz != '.') && p < ep;
330 			 p++) {
331 			cz += one_mbtowc(p, (const char *)cz, MB_LEN_MAX);
332 			*p |= attributes;
333 		    }
334 		break;
335 
336 			/* lukem: new directory prompt code */
337 	    case '~':
338 	    case '/':
339 	    case '.':
340 	    case 'c':
341 	    case 'C':
342 		Scp = *cp;
343 		if (Scp == 'c')		/* store format type (c == .) */
344 		    Scp = '.';
345 		if ((z = varval(STRcwd)) == STRNULL)
346 		    break;		/* no cwd, so don't do anything */
347 
348 			/* show ~ whenever possible - a la dirs */
349 		if (Scp == '~' || Scp == '.' ) {
350 		    if (tlength == 0 || olddir != z) {
351 			olddir = z;		/* have we changed dir? */
352 			olduser = getusername(&olddir);
353 		    }
354 		    if (olduser)
355 			z = olddir;
356 		}
357 		updirs = pdirs = 0;
358 
359 			/* option to determine fixed # of dirs from path */
360 		if (Scp == '.' || Scp == 'C') {
361 		    int skip;
362 #ifdef WINNT_NATIVE
363 		    Char *oldz = z;
364 		    if (z[1] == ':') {
365 		    	*p++ = attributes | *z++;
366 		    	*p++ = attributes | *z++;
367 		    }
368 			if (*z == '/' && z[1] == '/') {
369 				*p++ = attributes | *z++;
370 				*p++ = attributes | *z++;
371 				do {
372 					*p++ = attributes | *z++;
373 				}while(*z != '/');
374 			}
375 #endif /* WINNT_NATIVE */
376 		    q = z;
377 		    while (*z)				/* calc # of /'s */
378 			if (*z++ == '/')
379 			    updirs++;
380 
381 #ifdef WINNT_NATIVE
382 		    /*
383 		     * for format type c, prompt will be following...
384 		     * c:/path                => c:/path
385 		     * c:/path/to             => c:to
386 		     * //machine/share        => //machine/share
387 		     * //machine/share/folder => //machine:folder
388 		     */
389 		    if (oldz[0] == '/' && oldz[1] == '/' && updirs > 1)
390 			*p++ = attributes | ':';
391 #endif /* WINNT_NATIVE */
392 		    if ((Scp == 'C' && *q != '/'))
393 			updirs++;
394 
395 		    if (cp[1] == '0') {			/* print <x> or ...  */
396 			pdirs = 1;
397 			cp++;
398 		    }
399 		    if (cp[1] >= '1' && cp[1] <= '9') {	/* calc # to skip  */
400 			skip = cp[1] - '0';
401 			cp++;
402 		    }
403 		    else
404 			skip = 1;
405 
406 		    updirs -= skip;
407 		    while (skip-- > 0) {
408 			while ((z > q) && (*z != '/'))
409 			    z--;			/* back up */
410 			if (skip && z > q)
411 			    z--;
412 		    }
413 		    if (*z == '/' && z != q)
414 			z++;
415 		} /* . || C */
416 
417 							/* print ~[user] */
418 		if ((olduser) && ((Scp == '~') ||
419 		     (Scp == '.' && (pdirs || (!pdirs && updirs <= 0))) )) {
420 		    *p++ = attributes | '~';
421 		    if (p >= ep) break;
422 		    for (q = olduser; *q; *p++ = attributes | *q++)
423 			if (p >= ep) break;
424 		}
425 
426 			/* RWM - tell you how many dirs we've ignored */
427 			/*       and add '/' at front of this         */
428 		if (updirs > 0 && pdirs) {
429 		    if (p >= ep - 5) break;
430 		    if (adrof(STRellipsis)) {
431 			*p++ = attributes | '.';
432 			*p++ = attributes | '.';
433 			*p++ = attributes | '.';
434 		    } else {
435 			*p++ = attributes | '/';
436 			*p++ = attributes | '<';
437 			if (updirs > 9) {
438 			    *p++ = attributes | '9';
439 			    *p++ = attributes | '+';
440 			} else
441 			    *p++ = attributes | ('0' + updirs);
442 			*p++ = attributes | '>';
443 		    }
444 		}
445 
446 		for (; *z ; *p++ = attributes | *z++)
447 		    if (p >= ep) break;
448 		break;
449 			/* lukem: end of new directory prompt code */
450 
451 	    case 'n':
452 #ifndef HAVENOUTMP
453 		if (what == FMT_WHO) {
454 		    cz = (const unsigned char *) who_info(info, 'n',
455 			(char *) cbuff, sizeof(cbuff));
456 		    for (; *cz && p < ep; p++) {
457 			cz += one_mbtowc(p, (const char *)cz, MB_LEN_MAX);
458 			*p |= attributes;
459 		    }
460 		}
461 		else
462 #endif /* HAVENOUTMP */
463 		{
464 		    if ((z = varval(STRuser)) != STRNULL)
465 			for (; *z; *p++ = attributes | *z++)
466 			    if (p >= ep) break;
467 		}
468 		break;
469 	    case 'l':
470 #ifndef HAVENOUTMP
471 		if (what == FMT_WHO) {
472 		    cz = (const unsigned char *) who_info(info, 'l',
473 			(char *) cbuff, sizeof(cbuff));
474 		    for (; *cz && p < ep; p++) {
475 			cz += one_mbtowc(p, (const char *)cz, MB_LEN_MAX);
476 			*p |= attributes;
477 		    }
478 		}
479 		else
480 #endif /* HAVENOUTMP */
481 		{
482 		    if ((z = varval(STRtty)) != STRNULL)
483 			for (; *z; *p++ = attributes | *z++)
484 			    if (p >= ep) break;
485 		}
486 		break;
487 	    case 'd':
488 		for (cz = (const unsigned char *) day_list[t->tm_wday];
489 		     *cz && p < ep; p++) {
490 		    cz += one_mbtowc(p, (const char *)cz, MB_LEN_MAX);
491 		    *p |= attributes;
492 		}
493 		break;
494 	    case 'D':
495 		if (p >= ep - 3) break;
496 		p = Itoa(t->tm_mday, p, 2, attributes);
497 		break;
498 	    case 'w':
499 		if (p >= ep - 5) break;
500 		for (cz = (const unsigned char *) month_list[t->tm_mon];
501 		    *cz && p < ep; p++) {
502 		    cz += one_mbtowc(p, (const char *)cz, MB_LEN_MAX);
503 		    *p |= attributes;
504 		}
505 		break;
506 	    case 'W':
507 		if (p >= ep - 3) break;
508 		p = Itoa(t->tm_mon + 1, p, 2, attributes);
509 		break;
510 	    case 'y':
511 		if (p >= ep - 3) break;
512 		p = Itoa(t->tm_year % 100, p, 2, attributes);
513 		break;
514 	    case 'Y':
515 		if (p >= ep - 5) break;
516 		p = Itoa(t->tm_year + 1900, p, 4, attributes);
517 		break;
518 	    case 'S':		/* start standout */
519 		attributes |= STANDOUT;
520 		break;
521 	    case 'B':		/* start bold */
522 		attributes |= BOLD;
523 		break;
524 	    case 'U':		/* start underline */
525 		attributes |= UNDER;
526 		break;
527 	    case 's':		/* end standout */
528 		attributes &= ~STANDOUT;
529 		break;
530 	    case 'b':		/* end bold */
531 		attributes &= ~BOLD;
532 		break;
533 	    case 'u':		/* end underline */
534 		attributes &= ~UNDER;
535 		break;
536 	    case 'L':
537 		ClearToBottom();
538 		break;
539 
540 	    case 'j':
541 		{
542 		    Char xbuf[128], *ebuf, *xq;
543 		    int njobs = -1;
544 		    struct process *pp;
545 		    for (pp = proclist.p_next; pp; pp = pp->p_next)
546 			njobs++;
547 		    /* make sure we have space */
548 		    ebuf = Itoa(njobs, buf, 1, attributes);
549 		    for (xq = xbuf; xq < ebuf; *p++ = *xq++)
550 			if (p >= ep) break;
551 		    break;
552 		}
553 	    case '?':
554 		if ((z = varval(STRstatus)) != STRNULL)
555 		    for (; *z; *p++ = attributes | *z++)
556 			if (p >= ep) break;
557 		break;
558 	    case '$':
559 		sz = ep - p;
560 		(void) expdollar(&p, &cp, &sz, attributes);
561 		/* cp should point the last char of currnet % sequence */
562 		cp--;
563 		break;
564 	    case '%':
565 		*p++ = attributes | '%';
566 		break;
567 	    case '{':		/* literal characters start */
568 #if LITERAL == 0
569 		/*
570 		 * No literal capability, so skip all chars in the literal
571 		 * string
572 		 */
573 		while (*cp != '\0' && (*cp != '%' || cp[1] != '}'))
574 		    cp++;
575 #endif				/* LITERAL == 0 */
576 		attributes |= LITERAL;
577 		break;
578 	    case '}':		/* literal characters end */
579 		attributes &= ~LITERAL;
580 		break;
581 	    default:
582 #ifndef HAVENOUTMP
583 		if (*cp == 'a' && what == FMT_WHO) {
584 		    cz = (const unsigned char *) who_info(info, 'a',
585 			(char *) cbuff, sizeof(cbuff));
586 		    for (; *cz && p < ep; p++) {
587 			cz += one_mbtowc(p, (const char *)cz, MB_LEN_MAX);
588 			*p |= attributes;
589 		    }
590 		}
591 		else
592 #endif /* HAVENOUTMP */
593 		{
594 		    if (p >= ep - 3) break;
595 		    *p++ = attributes | '%';
596 		    *p++ = attributes | *cp;
597 		}
598 		break;
599 	    }
600 	}
601 	else if (*cp == '\\' || *cp == '^')
602 	    *p++ = attributes | parseescape(&cp);
603 	else if (*cp == HIST) {	/* EGS: handle '!'s in prompts */
604 	    if (what == FMT_HISTORY)
605 		fmthist('h', info, (char *) cbuff, sizeof(cbuff));
606 	    else
607 		(void) xsnprintf((char *) cbuff, sizeof(cbuff), "%d", eventno + 1);
608 	    for (cz = cbuff; *cz && p < ep; p++) {
609 		cz += one_mbtowc(p, (const char *)cz, MB_LEN_MAX);
610 		*p |= attributes;
611 	    }
612 	}
613 	else
614 	    *p++ = attributes | *cp;	/* normal character */
615     }
616     *p = '\0';
617 }
618 
619 Char *
620 expdollar(dstp, srcp, spp, attr)
621     Char **dstp;
622     const Char **srcp;
623     size_t *spp;
624     int	    attr;
625 {
626     struct varent *vp;
627     Char var[MAXVARLEN];
628     const Char *src = *srcp;
629     Char *val;
630     Char *dst = *dstp;
631     int i, curly = 0;
632 
633     /* found a variable, expand it */
634     for (i = 0; i < MAXVARLEN; i++) {
635 	var[i] = *++src & TRIM;
636 	if (i == 0 && var[i] == '{') {
637 	    curly = 1;
638 	    var[i] = *++src & TRIM;
639 	}
640 	if (!alnum(var[i])) {
641 
642 	    var[i] = '\0';
643 	    break;
644 	}
645     }
646     if (curly && (*src & TRIM) == '}')
647 	src++;
648 
649     vp = adrof(var);
650     val = (!vp) ? tgetenv(var) : NULL;
651     if (vp && vp->vec) {
652 	for (i = 0; vp->vec[i] != NULL; i++) {
653 	    for (val = vp->vec[i]; *spp > 0 && *val; (*spp)--)
654 		*dst++ = *val++ | attr;
655 	    if (vp->vec[i+1] && *spp > 0) {
656 		*dst++ = ' ' | attr;
657 		(*spp)--;
658 	    }
659 	}
660     }
661     else if (val) {
662 	for (; *spp > 0 && *val; (*spp)--)
663 	    *dst++ = *val++ | attr;
664     }
665     else {
666 	**dstp = '\0';
667 	*srcp = src;
668 	return NULL;
669     }
670     *dst = '\0';
671 
672     val = *dstp;
673     *srcp = src;
674     *dstp = dst;
675 
676     return val;
677 }
678